chore(pyproject): version bumping/relaxing (#886)

* chore(pyproject): version bumping/relaxing

* fix(pyproject): remove imaging extra for mkdocs-material

* fix: ruff formatting

* fix: mypy

* fix(pyproject): PySide violates SemVer

* chore(pyproject): set Python version, bump pydantic

* fix(ci): up Ruff to consistent version

* fix(pyproject): fixup requires-python

* fix: ruff checks

* chore(pyproject): bump dependencies

* fix(ci): up Ruff to consistent version

* fix(tests): strip out non-reproducible tests
This commit is contained in:
Xarvex
2025-05-05 14:47:57 -05:00
committed by GitHub
parent efb062034d
commit 702ecd4118
26 changed files with 100 additions and 166 deletions

View File

@@ -12,9 +12,9 @@ jobs:
uses: actions/checkout@v4
- name: Execute Ruff format
uses: chartboost/ruff-action@v1
uses: astral-sh/ruff-action@v3
with:
version: 0.8.1
version: 0.11.0
args: format --check
ruff-check:
@@ -25,7 +25,7 @@ jobs:
uses: actions/checkout@v4
- name: Execute Ruff check
uses: chartboost/ruff-action@v1
uses: astral-sh/ruff-action@v3
with:
version: 0.8.1
version: 0.11.8
args: check

View File

@@ -30,21 +30,18 @@
{
packages =
let
pythonPackages = pkgs.python312Packages;
python3Packages = pkgs.python312Packages;
pillow-jxl-plugin = pythonPackages.callPackage ./nix/package/pillow-jxl-plugin.nix {
pillow-jxl-plugin = python3Packages.callPackage ./nix/package/pillow-jxl-plugin.nix {
inherit (pkgs) cmake;
inherit pyexiv2;
inherit (pkgs) rustPlatform;
};
pyexiv2 = pythonPackages.callPackage ./nix/package/pyexiv2.nix { inherit (pkgs) exiv2; };
vtf2img = pythonPackages.callPackage ./nix/package/vtf2img.nix { };
pyexiv2 = python3Packages.callPackage ./nix/package/pyexiv2.nix { inherit (pkgs) exiv2; };
vtf2img = python3Packages.callPackage ./nix/package/vtf2img.nix { };
in
rec {
default = tagstudio;
tagstudio = pythonPackages.callPackage ./nix/package {
inherit pillow-jxl-plugin vtf2img;
};
tagstudio = pkgs.callPackage ./nix/package { inherit pillow-jxl-plugin vtf2img; };
tagstudio-jxl = tagstudio.override { withJXLSupport = true; };
inherit pillow-jxl-plugin pyexiv2 vtf2img;

View File

@@ -1,44 +1,22 @@
{
buildPythonApplication,
chardet,
ffmpeg-headless,
ffmpeg-python,
hatchling,
humanfriendly,
lib,
mutagen,
numpy,
opencv-python,
pillow,
pillow-heif,
pillow-jxl-plugin,
pipewire,
pydantic,
pydub,
pyside6,
pytest-qt,
pytest-xdist,
pytestCheckHook,
pythonRelaxDepsHook,
python3Packages,
qt6,
rawpy,
send2trash,
sqlalchemy,
stdenv,
structlog,
syrupy,
toml,
ujson,
vtf2img,
wrapGAppsHook,
pillow-jxl-plugin,
vtf2img,
withJXLSupport ? false,
}:
let
pyproject = (lib.importTOML ../../pyproject.toml).project;
in
buildPythonApplication {
python3Packages.buildPythonApplication {
pname = pyproject.name;
inherit (pyproject) version;
pyproject = true;
@@ -46,7 +24,7 @@ buildPythonApplication {
src = ../../.;
nativeBuildInputs = [
pythonRelaxDepsHook
python3Packages.pythonRelaxDepsHook
qt6.wrapQtAppsHook
# INFO: Should be unnecessary once PR is pulled.
@@ -59,7 +37,7 @@ buildPythonApplication {
qt6.qtmultimedia
];
nativeCheckInputs = [
nativeCheckInputs = with python3Packages; [
pytest-qt
pytest-xdist
pytestCheckHook
@@ -80,30 +58,41 @@ buildPythonApplication {
lib.makeLibraryPath [ pipewire ]
}";
pythonRemoveDeps = true;
pythonRemoveDeps = lib.optional (!withJXLSupport) [ "pillow_jxl" ];
pythonRelaxDeps = [
"numpy"
"pillow"
"pillow-heif"
"pillow-jxl-plugin"
"structlog"
"typing-extensions"
];
pythonImportsCheck = [ "tagstudio" ];
build-system = [ hatchling ];
dependencies = [
chardet
ffmpeg-python
humanfriendly
mutagen
numpy
opencv-python
pillow
pillow-heif
pydantic
pydub
pyside6
rawpy
send2trash
sqlalchemy
structlog
toml
ujson
vtf2img
] ++ lib.optional withJXLSupport pillow-jxl-plugin;
build-system = with python3Packages; [ hatchling ];
dependencies =
with python3Packages;
[
chardet
ffmpeg-python
humanfriendly
mutagen
numpy
opencv-python
pillow
pillow-heif
pydantic
pydub
pyside6
rawpy
send2trash
sqlalchemy
structlog
toml
ujson
vtf2img
]
++ lib.optional withJXLSupport pillow-jxl-plugin;
disabledTests = [
# INFO: These tests require modifications to a library, which does not work

View File

@@ -8,42 +8,43 @@ description = "A User-Focused Photo & File Management System."
version = "9.5.2"
license = "GPL-3.0-only"
readme = "README.md"
requires-python = ">=3.12,<3.13"
dependencies = [
"chardet==5.2.0",
"ffmpeg-python==0.2.0",
"humanfriendly==10.0",
"mutagen==1.47.0",
"numpy==2.1.0",
"opencv_python==4.10.0.84",
"Pillow==10.3.0",
"pillow-heif==0.16.0",
"pillow-jxl-plugin==1.3.0",
"pydub==0.25.1",
"PySide6==6.8.0.1",
"rawpy==0.22.0",
"Send2Trash==1.8.3",
"SQLAlchemy==2.0.34",
"structlog==24.4.0",
"typing_extensions>=3.10.0.0,<4.11.0",
"ujson>=5.8.0,<5.9.0",
"vtf2img==0.1.0",
"toml==0.10.2",
"pydantic==2.9.2",
"chardet~=5.2",
"ffmpeg-python~=0.2",
"humanfriendly==10.*",
"mutagen~=1.47",
"numpy~=2.2",
"opencv_python~=4.11",
"Pillow~=11.2",
"pillow-heif~=0.22",
"pillow-jxl-plugin~=1.3",
"pydantic~=2.10",
"pydub~=0.25",
"PySide6==6.8.0.*",
"rawpy~=0.24",
"Send2Trash~=1.8",
"SQLAlchemy~=2.0",
"structlog~=25.3",
"toml~=0.10",
"typing_extensions~=4.13",
"ujson~=5.10",
"vtf2img~=0.1",
]
[project.optional-dependencies]
dev = ["tagstudio[mkdocs,mypy,pre-commit,pyinstaller,pytest,ruff]"]
mkdocs = ["mkdocs-material[imaging]==9.*"]
mypy = ["mypy==1.11.2", "mypy-extensions==1.*", "types-ujson>=5.8.0,<5.9.0"]
pre-commit = ["pre-commit==3.7.0"]
pyinstaller = ["Pyinstaller==6.6.0"]
mkdocs = ["mkdocs-material==9.*"]
mypy = ["mypy==1.15.0", "mypy-extensions==1.*", "types-ujson~=5.10"]
pre-commit = ["pre-commit~=4.2"]
pyinstaller = ["Pyinstaller~=6.13"]
pytest = [
"pytest==8.2.0",
"pytest-cov==5.0.0",
"pytest==8.3.5",
"pytest-cov==6.1.1",
"pytest-qt==4.4.0",
"syrupy==4.7.1",
"syrupy==4.9.1",
]
ruff = ["ruff==0.8.1"]
ruff = ["ruff==0.11.8"]
[project.gui-scripts]
tagstudio = "tagstudio.main:main"

View File

@@ -41,7 +41,7 @@ class FfmpegChecker(QMessageBox):
red = get_ui_color(ColorType.PRIMARY, UiColor.RED)
green = get_ui_color(ColorType.PRIMARY, UiColor.GREEN)
missing = f"<span style='color:{red}'>{Translations["generic.missing"]}</span>"
missing = f"<span style='color:{red}'>{Translations['generic.missing']}</span>"
found = f"<span style='color:{green}'>{Translations['about.module.found']}</span>"
status = Translations.format(
"ffmpeg.missing.status",
@@ -50,4 +50,4 @@ class FfmpegChecker(QMessageBox):
ffprobe=ffprobe,
ffprobe_status=found if which(FFPROBE_CMD) else missing,
)
self.setText(f"{Translations["ffmpeg.missing.description"]}<br><br>{status}")
self.setText(f"{Translations['ffmpeg.missing.description']}<br><br>{status}")

View File

@@ -2,7 +2,8 @@
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from typing import TYPE_CHECKING, Callable, override
from collections.abc import Callable
from typing import TYPE_CHECKING, override
import structlog
from PySide6 import QtCore, QtGui

View File

@@ -4,8 +4,9 @@
import math
from collections.abc import Callable
from pathlib import Path
from typing import Callable, override
from typing import override
from warnings import catch_warnings
import structlog

View File

@@ -616,7 +616,7 @@ class JsonMigrationModal(QObject):
logger.info(
"[Field Comparison]",
fields="\n".join([str(x) for x in zip(json_fields, sql_fields)]),
fields="\n".join([str(x) for x in zip(json_fields, sql_fields, strict=False)]),
)
self.field_parity = True

View File

@@ -3,7 +3,8 @@
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from typing import Callable, override
from collections.abc import Callable
from typing import override
import structlog
from PySide6 import QtCore, QtGui

View File

@@ -315,7 +315,7 @@ class FieldContainers(QWidget):
# Normalize line endings in any text content.
if not is_mixed:
assert isinstance(field.value, (str, type(None)))
assert isinstance(field.value, str | type(None))
text = field.value or ""
else:
text = "<i>Mixed Data</i>"
@@ -355,7 +355,7 @@ class FieldContainers(QWidget):
container.set_inline(False)
# Normalize line endings in any text content.
if not is_mixed:
assert isinstance(field.value, (str, type(None)))
assert isinstance(field.value, str | type(None))
text = (field.value or "").replace("\r", "\n")
else:
text = "<i>Mixed Data</i>"
@@ -514,7 +514,7 @@ class FieldContainers(QWidget):
"""Update a field in all selected Entries, given a field object."""
assert isinstance(
field,
(TextField, DatetimeField),
TextField | DatetimeField,
), f"instance: {type(field)}"
entry_ids = [e.id for e in self.cached_entries]

View File

@@ -165,11 +165,11 @@ class FileAttributes(QWidget):
for i, part in enumerate(display_path.parts):
part_ = part.strip(os.path.sep)
if i != len(display_path.parts) - 1:
file_str += f"{"\u200b".join(part_)}{separator}</b>"
file_str += f"{'\u200b'.join(part_)}{separator}</b>"
else:
if file_str != "":
file_str += "<br>"
file_str += f"<b>{"\u200b".join(part_)}</b>"
file_str += f"<b>{'\u200b'.join(part_)}</b>"
self.file_label.setText(file_str)
self.file_label.setCursor(Qt.CursorShape.PointingHandCursor)
self.opener = FileOpenerHelper(filepath)

View File

@@ -3,7 +3,7 @@
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from typing import Callable
from collections.abc import Callable
from PySide6.QtCore import Qt, QThreadPool
from PySide6.QtWidgets import QProgressDialog, QVBoxLayout, QWidget

View File

@@ -3,7 +3,7 @@
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from typing import Callable
from collections.abc import Callable
from PySide6.QtWidgets import QLineEdit, QVBoxLayout

View File

@@ -11,6 +11,7 @@ import zipfile
from copy import deepcopy
from io import BytesIO
from pathlib import Path
from typing import cast
from warnings import catch_warnings
import cv2
@@ -754,8 +755,8 @@ class ThumbRenderer(QObject):
data = np.asarray(raw.getchannel(0))
m, n = data.shape[:2]
col: np.ndarray = data.any(0)
row: np.ndarray = data.any(1)
col: np.ndarray = cast(np.ndarray, data.any(0))
row: np.ndarray = cast(np.ndarray, data.any(1))
cropped_data = np.asarray(raw)[
row.argmax() : m - row[::-1].argmax(),
col.argmax() : n - col[::-1].argmax(),
@@ -802,7 +803,7 @@ class ThumbRenderer(QObject):
bg = Image.new("RGBA", (size, size), color="#00000000")
draw = ImageDraw.Draw(bg)
lines_of_padding = 2
y_offset = 0
y_offset = 0.0
for font_size in scaled_sizes:
font = ImageFont.truetype(filepath, size=font_size)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 739 739" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M392.721,715.585C422.869,745.733 471.822,745.733 501.97,715.585C521.252,696.304 698.205,519.351 715.585,501.97C745.733,471.822 745.733,422.869 715.585,392.721C702.356,379.491 380.066,58.201 366.836,44.972C353.638,31.774 336.836,24.354 319.586,22.707C305.002,21.315 93.263,0.703 81.113,0.097C60.067,-0.954 38.675,6.558 22.616,22.616C7.307,37.926 -0.233,58.083 0.005,78.166C0.158,90.957 21.834,301.853 22.429,315.577C23.238,334.217 30.753,352.617 44.972,366.836C64.714,386.578 372.979,695.844 392.721,715.585ZM221.206,462.624C199.538,440.957 109.62,350.947 109.62,350.947C95.375,336.702 95.375,315.292 109.62,301.047L301.047,109.62C315.292,95.375 336.702,95.375 350.947,109.62C350.947,109.62 557.592,316.079 616.139,374.813C679.714,438.589 568.691,438.77 585.773,495.797C588.986,506.52 600.846,531.699 594.432,548.233C579.351,587.118 529.349,575.43 521.799,548.233C515.39,525.148 528.92,514.925 524.759,499.813C517.88,474.829 491.657,481.134 483.903,499.813C458.989,559.836 503.029,591.37 493.423,628.195C479.788,680.463 410.137,673.342 400.675,628.195C392.608,589.708 425.265,574.286 421.93,520.583C419.956,488.804 394.552,464.882 377.567,488.023C360.792,510.879 402.256,542.428 373.64,575.065C341.566,611.644 283.344,579.898 301.047,528.499C311.597,497.869 326.263,466.649 311.901,462.624C282.374,454.351 267.942,509.361 221.206,462.624ZM180.995,84.554C205.968,109.528 205.968,150.079 180.995,175.052C156.022,200.026 115.471,200.026 90.497,175.052C65.524,150.079 65.524,109.528 90.497,84.554C115.471,59.581 156.022,59.581 180.995,84.554Z"/>
<g transform="matrix(0.986683,0,0,0.986683,-136.081,-136.081)">
<path d="M733.962,560.164C740.987,553.139 740.987,541.733 733.962,534.708L482.173,282.92C475.148,275.895 463.742,275.895 456.717,282.92L431.261,308.376C424.237,315.4 424.237,326.807 431.261,333.831L683.05,585.62C690.075,592.645 701.481,592.645 708.506,585.62L733.962,560.164ZM439.639,559.207C446.664,552.182 446.664,540.776 439.639,533.751L335.491,429.602C328.466,422.578 317.059,422.578 310.035,429.602L284.579,455.058C277.554,462.083 277.554,473.489 284.579,480.514L388.728,584.663C395.752,591.688 407.159,591.688 414.184,584.663L439.639,559.207ZM584.306,556.624C591.331,549.599 591.331,538.192 584.306,531.168L409.115,355.977C402.091,348.953 390.684,348.953 383.66,355.977L358.204,381.433C351.179,388.458 351.179,399.864 358.204,406.889L533.394,582.079C540.419,589.104 551.825,589.104 558.85,582.079L584.306,556.624ZM298.425,246.543C311.081,259.198 311.081,279.747 298.425,292.402C285.77,305.058 265.221,305.058 252.566,292.402C239.911,279.747 239.911,259.198 252.566,246.543C265.221,233.888 285.77,233.888 298.425,246.543Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -79,11 +79,11 @@ def test_file_path_display(
for i, part in enumerate(display_path.parts):
part_ = part.strip(os.path.sep)
if i != len(display_path.parts) - 1:
file_str += f"{"\u200b".join(part_)}{separator}</b>"
file_str += f"{'\u200b'.join(part_)}{separator}</b>"
else:
if file_str != "":
file_str += "<br>"
file_str += f"<b>{"\u200b".join(part_)}</b>"
file_str += f"<b>{'\u200b'.join(part_)}</b>"
# Assert the file path is displayed correctly
assert panel.file_attrs.file_label.text() == file_str

View File

@@ -1,49 +0,0 @@
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import io
from functools import partial
from pathlib import Path
import pytest
from PIL import Image
from syrupy.extensions.image import PNGImageSnapshotExtension
from tagstudio.qt.widgets.thumb_renderer import ThumbRenderer
@pytest.mark.parametrize(
["fixture_file", "thumbnailer"],
[
(
"sample.odt",
ThumbRenderer._open_doc_thumb,
),
(
"sample.ods",
ThumbRenderer._open_doc_thumb,
),
(
"sample.epub",
ThumbRenderer._epub_cover,
),
(
"sample.pdf",
partial(ThumbRenderer._pdf_thumb, size=200),
),
(
"sample.svg",
partial(ThumbRenderer._image_vector_thumb, size=200),
),
],
)
def test_preview_render(cwd, fixture_file, thumbnailer, snapshot):
file_path: Path = cwd / "fixtures" / fixture_file
img: Image.Image = thumbnailer(file_path)
img_bytes = io.BytesIO()
img.save(img_bytes, format="PNG")
img_bytes.seek(0)
assert img_bytes.read() == snapshot(extension_class=PNGImageSnapshotExtension)