From 7d086a7a208b459d49b130e4856b3133382c9a41 Mon Sep 17 00:00:00 2001 From: Florian Greistorfer Date: Fri, 6 Feb 2026 08:00:53 +0100 Subject: [PATCH] types --- StaticGalleryBuilder.code-workspace | 19 ++++++----- modules/argumentparser.py | 4 +-- modules/cclicense.py | 2 +- modules/generate_html.py | 15 +++++--- modules/logger.py | 2 +- modules/svg_handling.py | 53 +++++++++++------------------ 6 files changed, 44 insertions(+), 51 deletions(-) diff --git a/StaticGalleryBuilder.code-workspace b/StaticGalleryBuilder.code-workspace index 8bec00d..2dbbdcd 100644 --- a/StaticGalleryBuilder.code-workspace +++ b/StaticGalleryBuilder.code-workspace @@ -129,8 +129,6 @@ "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" }, - "black-formatter.args": ["-l 260"], - "black-formatter.interpreter": ["/usr/bin/python3"], "editor.formatOnSave": false, "emmet.includeLanguages": { "jinja-css": "css", @@ -157,7 +155,9 @@ "json.schemaDownload.enable": true, "json.schemas": [ { - "fileMatch": ["manifest.json.j2"], + "fileMatch": [ + "manifest.json.j2" + ], "url": "https://json.schemastore.org/web-manifest-combined.json" } ], @@ -169,9 +169,10 @@ "packageManager": "ms-python.python:pip" } ], - "python.analysis.inlayHints.callArgumentNames": "off", - "python.analysis.inlayHints.functionReturnTypes": false, - "python.analysis.inlayHints.variableTypes": false, + "python.analysis.inlayHints.callArgumentNames": "all", + "python.analysis.inlayHints.functionReturnTypes": true, + "python.analysis.inlayHints.variableTypes": true, + "python.analysis.typeCheckingMode": "standard", "yaml.schemas": { "https://raw.githubusercontent.com/pamburus/hl/master/schema/json/config.schema.json": "file:///home/user/git/github.com/greflm13/StaticGalleryBuilder/hl_config.yaml" }, @@ -228,7 +229,9 @@ "kind": "build", "isDefault": true }, - "dependsOn": ["Clean"] + "dependsOn": [ + "Clean" + ] }, { "command": "rm -rf build dist", @@ -278,4 +281,4 @@ } ] } -} +} \ No newline at end of file diff --git a/modules/argumentparser.py b/modules/argumentparser.py index a317866..043a50c 100644 --- a/modules/argumentparser.py +++ b/modules/argumentparser.py @@ -125,7 +125,7 @@ def parse_arguments(version: str) -> Args: """ # fmt: off if RICH: - parser = configargparse.ArgumentParser(default_config_files=[CONFIGPATH], add_config_file_help=False, description="generate HTML files for a static image hosting website", formatter_class=RichHelpFormatter) + parser = configargparse.ArgumentParser(default_config_files=[CONFIGPATH], add_config_file_help=False, description="generate HTML files for a static image hosting website", formatter_class=RichHelpFormatter) # pyright: ignore[reportPossiblyUnboundVariable] else: parser = configargparse.ArgumentParser(default_config_files=[CONFIGPATH], add_config_file_help=False, description="generate HTML files for a static image hosting website") parser.add_argument("-a", "--author-name", help="name of the author of the images", default=DEFAULT_AUTHOR, type=str, dest="author_name", metavar="AUTHOR") @@ -140,7 +140,7 @@ def parse_arguments(version: str) -> Args: parser.add_argument("--exclude-folder", help="folders to exclude from processing, globs supported (can be specified multiple times)", action="append", dest="exclude_folders", metavar="FOLDER") parser.add_argument("--folderthumbnails", help="generate subfolder thumbnails (first image in folder will be shown)", action="store_true", default=False, dest="folder_thumbs") if RICH: - parser.add_argument("--generate-help-preview", action=HelpPreviewAction, path="help.svg") + parser.add_argument("--generate-help-preview", action=HelpPreviewAction, path="help.svg") # pyright: ignore[reportPossiblyUnboundVariable] parser.add_argument("--ignore-other-files", help="ignore files that do not match the specified extensions", action="store_true", default=False, dest="ignore_other_files") parser.add_argument("--ignore-extension", help="file extensions to ignore (can be specified multiple times)", action="append", default=[], dest="ignore_extensions", metavar="EXTENSION") parser.add_argument("--regenerate-thumbnails", help="regenerate thumbnails even if they already exist", action="store_true", default=False, dest="regenerate_thumbnails") diff --git a/modules/cclicense.py b/modules/cclicense.py index 87966bc..dc461d0 100644 --- a/modules/cclicense.py +++ b/modules/cclicense.py @@ -128,4 +128,4 @@ def licensepicswitch(cclicense: str) -> list[str]: ], } - return switch.get(cclicense, "") + return switch.get(cclicense, []) diff --git a/modules/generate_html.py b/modules/generate_html.py index 404c1c2..b3f6064 100644 --- a/modules/generate_html.py +++ b/modules/generate_html.py @@ -138,9 +138,14 @@ def update_metadata(metadata: Metadata, folder: str) -> None: """ metadata_path = os.path.join(folder, ".metadata.json") if metadata: - with open(metadata_path, "w", encoding="utf-8") as metadatafile: - logger.info("writing metadata file", extra={"file": metadata_path}) - metadatafile.write(json.dumps(metadata.to_dict(), indent=4)) + if os.path.exists(metadata_path): + logger.info("updating metadata file", extra={"file": metadata_path}) + with open(metadata_path, "w", encoding="utf-8") as metadatafile: + metadatafile.write(json.dumps(metadata.to_dict(), indent=4)) + else: + logger.info("creating metadata file", extra={"file": metadata_path}) + with open(metadata_path, "x", encoding="utf-8") as metadatafile: + metadatafile.write(json.dumps(metadata.to_dict(), indent=4)) else: if os.path.exists(metadata_path): logger.info("deleting empty metadata file", extra={"file": metadata_path}) @@ -257,11 +262,11 @@ def get_image_info(item: str, folder: str) -> ImageMetadata: return ImageMetadata(w=width, h=height, tags=tags, exifdata=exifdata, xmp=xmp, src="", msrc="", name="", title="") -def nested_dict(): +def nested_dict() -> defaultdict[Any, Any]: return defaultdict(nested_dict) -def insert_path(d, path): +def insert_path(d, path) -> None: for part in path[:-1]: d = d[part] last = path[-1] diff --git a/modules/logger.py b/modules/logger.py index c13baf9..d76ba9e 100644 --- a/modules/logger.py +++ b/modules/logger.py @@ -18,7 +18,7 @@ import gzip import shutil import logging from datetime import datetime -from pythonjsonlogger import json as jsonlogger +from pythonjsonlogger import jsonlogger # Constants for file paths and exclusions SCRIPTDIR = os.path.dirname(os.path.realpath(__file__)).removesuffix(__package__ if __package__ else "") diff --git a/modules/svg_handling.py b/modules/svg_handling.py index fd39773..d945a0e 100644 --- a/modules/svg_handling.py +++ b/modules/svg_handling.py @@ -1,5 +1,6 @@ import os import shutil +from dataclasses import dataclass from subprocess import Popen, PIPE from PIL import Image from jinja2 import Environment, FileSystemLoader @@ -26,6 +27,7 @@ ICON_SIZES = ["36x36", "48x48", "72x72", "96x96", "144x144", "192x192", "512x512 env = Environment(loader=FileSystemLoader(os.path.join(SCRIPTDIR, "templates"))) +@dataclass class Icon: src: str type: str @@ -68,11 +70,12 @@ def save_png_icon(content: str, iconspath: str) -> None: iconspath : str Path to the directory where the PNG icon will be saved. """ - tmpimg = BytesIO() - cairosvg.svg2png(bytestring=content, write_to=tmpimg) - with Image.open(tmpimg) as iconfile: - logger.info("saving png icon", extra={"iconspath": iconspath}) - iconfile.save(os.path.join(iconspath, "icon.png")) + if SVGSUPPORT: + tmpimg = BytesIO() # pyright: ignore[reportPossiblyUnboundVariable] + cairosvg.svg2png(bytestring=content, write_to=tmpimg) # pyright: ignore[reportPossiblyUnboundVariable] + with Image.open(tmpimg) as iconfile: + logger.info("saving png icon", extra={"iconspath": iconspath}) + iconfile.save(os.path.join(iconspath, "icon.png")) def generate_favicon(iconspath: str, root_directory: str) -> None: @@ -176,40 +179,20 @@ def create_icons_from_svg(files: list[str], iconspath: str, _args: Args) -> list svg = [file for file in files if file.endswith(".svg")][0] logger.info("creating icons for web application", extra={"iconspath": iconspath, "svg": svg}) icon_list = [ - {"src": f"{_args.web_root_url}.static/icons/{svg}", "type": "image/svg+xml", "sizes": "512x512", "purpose": "maskable"}, - {"src": f"{_args.web_root_url}.static/icons/{svg}", "type": "image/svg+xml", "sizes": "512x512", "purpose": "any"}, + Icon(src=f"{_args.web_root_url}.static/icons/{svg}", type="image/svg+xml", sizes="512x512", purpose="maskable"), + Icon(src=f"{_args.web_root_url}.static/icons/{svg}", type="image/svg+xml", sizes="512x512", purpose="any"), ] for size in ICON_SIZES: - tmpimg = BytesIO() + tmpimg = BytesIO() # pyright: ignore[reportPossiblyUnboundVariable] sizes = size.split("x") iconpath = os.path.join(iconspath, os.path.splitext(svg)[0] + "-" + size + ".png") logger.info("converting svg to png", extra={"svg": svg, "size": size}) - cairosvg.svg2png( - url=os.path.join(iconspath, svg), - write_to=tmpimg, - output_width=int(sizes[0]), - output_height=int(sizes[1]), - scale=1, - ) + cairosvg.svg2png(url=os.path.join(iconspath, svg), write_to=tmpimg, output_width=int(sizes[0]), output_height=int(sizes[1]), scale=1) # pyright: ignore[reportPossiblyUnboundVariable] with Image.open(tmpimg) as iconfile: logger.info("saving png file", extra={"iconpath": iconpath}) iconfile.save(iconpath, format="PNG") - icon_list.append( - { - "src": f"{_args.web_root_url}.static/icons/{os.path.splitext(svg)[0]}-{size}.png", - "sizes": size, - "type": "image/png", - "purpose": "maskable", - } - ) - icon_list.append( - { - "src": f"{_args.web_root_url}.static/icons/{os.path.splitext(svg)[0]}-{size}.png", - "sizes": size, - "type": "image/png", - "purpose": "any", - } - ) + icon_list.append(Icon(src=f"{_args.web_root_url}.static/icons/{os.path.splitext(svg)[0]}-{size}.png", sizes=size, type="image/png", purpose="maskable")) + icon_list.append(Icon(src=f"{_args.web_root_url}.static/icons/{os.path.splitext(svg)[0]}-{size}.png", sizes=size, type="image/png", purpose="any")) return icon_list @@ -236,8 +219,8 @@ def create_icons_from_png(iconspath: str, web_root_url: str) -> list[Icon]: with Image.open(os.path.join(iconspath, icon)) as iconfile: iconsize = f"{iconfile.size[0]}x{iconfile.size[1]}" logger.info("using icon", extra={"iconspath": iconspath, "icon": icon, "size": iconsize}) - icon_list.append({"src": f"{web_root_url}.static/icons/{icon}", "sizes": iconsize, "type": "image/png", "purpose": "maskable"}) - icon_list.append({"src": f"{web_root_url}.static/icons/{icon}", "sizes": iconsize, "type": "image/png", "purpose": "any"}) + icon_list.append(Icon(src=f"{web_root_url}.static/icons/{icon}", sizes=iconsize, type="image/png", purpose="maskable")) + icon_list.append(Icon(src=f"{web_root_url}.static/icons/{icon}", sizes=iconsize, type="image/png", purpose="any")) return icon_list @@ -254,7 +237,9 @@ def webmanifest(_args: Args) -> None: iconspath = os.path.join(_args.root_directory, ".static", "icons") files = os.listdir(iconspath) - icon_list = create_icons_from_svg(files, iconspath, _args) if SVGSUPPORT and any(file.endswith(".svg") for file in files) else create_icons_from_png(iconspath, _args.web_root_url) + icon_list = ( + create_icons_from_svg(files, iconspath, _args) if SVGSUPPORT and any(file.endswith(".svg") for file in files) else create_icons_from_png(iconspath, _args.web_root_url) + ) if not icon_list: print("No icons found in the static/icons folder!")