Compare commits

..

1 Commits

Author SHA1 Message Date
Hosted Weblate 0719e1e886 Translated using Weblate (Japanese)
Currently translated at 98.6% (360 of 365 strings)

Co-authored-by: wany-oh <wany-oh@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ja/
Translation: TagStudio/Strings
2026-05-13 05:11:45 +02:00
34 changed files with 197 additions and 292 deletions
@@ -1,18 +1,23 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
---
name: Pyright
name: MyPy
on: [push, pull_request]
jobs:
pyright:
name: Run Pyright
mypy:
name: Run MyPy
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup reviewdog
uses: reviewdog/action-setup@v1
with:
reviewdog_version: latest
- name: Setup Python
uses: actions/setup-python@v5
with:
@@ -22,10 +27,10 @@ jobs:
- name: Install Python dependencies
run: |
python -m pip install --upgrade uv
uv pip install --system .[pyright,pytest]
uv pip install --system .[mypy]
- name: Execute Pyright
uses: jordemort/action-pyright@v1
- name: Execute MyPy
uses: tsuyoshicho/action-mypy@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: ${{ github.event_name == 'pull_request' && 'github-pr-review' || 'github-check' }}
fail_on_error: true
+3 -3
View File
@@ -2,9 +2,9 @@
repos:
- repo: local
hooks:
- id: pyright
name: pyright
entry: pyright
- id: mypy
name: mypy
entry: mypy
language: system
types_or: [python, pyi]
require_serial: true
+12 -23
View File
@@ -27,7 +27,7 @@ Thank you so much for showing interest in contributing to TagStudio! Here are a
- I've read the [FAQ](https://github.com/TagStudioDev/TagStudio/blob/main/README.md#faq)
- I've checked the project's [Issues](https://github.com/TagStudioDev/TagStudio/issues) and [Pull Requests](https://github.com/TagStudioDev/TagStudio/pulls)
- **I've created a new issue for my feature/fix _before_ starting work on it**, or have at least notified others in the relevant existing issue(s) of my intention to work on it
- I've set up my development environment including Ruff, Pyright, and Pytest
- I've set up my development environment including Ruff, Mypy, and PyTest
- I've read the CONTRIBUTING.md/Contributing page on the documentation site as well as the and/or [Style Guide](style.md)
- **_I mean it, I've found or created an issue for my feature/fix!_**
@@ -75,11 +75,7 @@ When pushing your code, several automated workflows will check it against predef
### [Ruff](https://github.com/astral-sh/ruff)
Ruff is a Python linter and code formatter that helps enforce a consistent formatting style across our codebase.
Ruff is installed alongside the `pip install -e ".[dev]"` command, but is also available as a VS Code [extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff), PyCharm [plugin](https://plugins.jetbrains.com/plugin/20574-ruff), and [more](https://docs.astral.sh/ruff/integrations/).
Our Ruff workflow runs whenever a pull request is opened or commits are pushed. Note that this workflow **does NOT** format code, but just serves to report any issues that have not been addressed by that point.
A Python linter and code formatter. Ruff uses the `pyproject.toml` as its config file and runs whenever code is pushed or pulled into the project.
#### Running Locally
@@ -91,31 +87,24 @@ Inside the root repository directory:
Ruff should automatically discover the configuration options inside the [pyproject.toml](https://github.com/TagStudioDev/TagStudio/blob/main/pyproject.toml) file. For more information, see the [ruff configuration discovery docs](https://docs.astral.sh/ruff/configuration/#config-file-discovery).
### [Pyright](https://github.com/microsoft/pyright)
Ruff is also available as a VS Code [extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff), PyCharm [plugin](https://plugins.jetbrains.com/plugin/20574-ruff), and [more](https://docs.astral.sh/ruff/integrations/).
Pyright is a static type checker for Python that helps enforce type strictness and prevent easy-to-miss errors across our codebase.
### [Mypy](https://github.com/python/mypy)
Pyright is installed alongside the `pip install -e ".[dev]"` command, but is also available as VS Code extensions (see the [Pyright](https://marketplace.visualstudio.com/items?itemName=ms-pyright.pyright), [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance), and [basedpyright](https://marketplace.visualstudio.com/items?itemName=detachhead.basedpyright) extensions), a PyCharm [setting](https://www.jetbrains.com/help/pycharm/lsp-tools.html#pyright), or in the form of forks such as [basedpyright](https://docs.basedpyright.com/latest/).
Our Pyright workflow runs whenever a pull request is opened or commits are pushed. Note that this **does NOT** format code or fix issues, but just serves to report any issues that have not been addressed by that point.
Mypy is a static type checker for Python. It sure has a lot to say sometimes, but we recommend you take its advice when possible. Mypy also uses the `pyproject.toml` as its config file and runs whenever code is pushed or pulled into the project.
#### Running Locally
- Run `pyright` to check code
- **(First time only)** Run the following:
- `mkdir -p .mypy_cache`
- `mypy --install-types --non-interactive`
- You can now check code by running `mypy --config-file pyproject.toml .` in the repository root. _(Don't forget the "." at the end!)_
Pyright/basedpyright should automatically discover the configuration options inside the [pyproject.toml](https://github.com/TagStudioDev/TagStudio/blob/main/pyproject.toml) file.
Mypy is also available as a VS Code [extension](https://marketplace.visualstudio.com/items?itemName=matangover.mypy), PyCharm [plugin](https://plugins.jetbrains.com/plugin/11086-mypy), and [more](https://plugins.jetbrains.com/plugin/11086-mypy).
### [Pytest](https://github.com/pytest-dev/pytest)
### PyTest
Pytest is our testing suite and runs all our Python code against the tests in the [`tests/`](https://github.com/TagStudioDev/TagStudio/tree/main/tests) directory.
Pytest is installed alongside the `pip install -e ".[dev]"` command.
Our Pytest workflow runs whenever a pull request is opened or commits are pushed.
#### Running Locally
- Run all tests by running `pytest tests/` in the repository root
- Run all tests by running `pytest tests/` in the repository root.
## Code Style
+1 -1
View File
@@ -146,7 +146,7 @@ The entry point for TagStudio is `src/tagstudio/main.py`. You can target this fi
### pre-commit
There is a [pre-commit](https://pre-commit.com/) configuration that will run through some checks before code is committed. Namely Pyright and Ruff will check your code, catching those nits right away.
There is a [pre-commit](https://pre-commit.com/) configuration that will run through some checks before code is committed. Namely, mypy and the Ruff linter and formatter will check your code, catching those nits right away.
Once you have pre-commit installed, just run:
+3 -12
View File
@@ -148,9 +148,9 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
#### Version 200
| Used From | Format | Location |
| ---------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
| [c15e2b5](https://github.com/TagStudioDev/TagStudio/commit/c15e2b56eedd0a3c13391fa43571b8f8f7c7a91f) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
| Used From | Format | Location |
| --------- | ------ | ----------------------------------------------- |
| TBD | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Adds `text_field_templates` and `date_field_templates` tables.
- Drops `boolean_fields` and `value_type` tables.
@@ -162,12 +162,3 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
- Values are set to `TRUE` if the field row was previously a "TEXT_BOX" type.
- Repairs existing "Description" fields inside the `text_fields` table to have their `is_multiline` column set to `TRUE` _(Previously done in [Version 7](#version-7))_.
- Repairs existing "Comments" fields inside the `text_fields` table to have their `is_multiline` column set to `TRUE`.
#### Version 201
| Used From | Format | Location |
| --------- | ------ | ----------------------------------------------- |
| TBD | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Drops `type_key` columns from `text_fields` and `datetime_fields` tables.
- Enforces column positions for `text_fields` and `datetime_fields` tables.
+30 -8
View File
@@ -42,17 +42,17 @@ dependencies = [
]
[project.optional-dependencies]
dev = ["tagstudio[mkdocs,pyright,pre-commit,pyinstaller,pytest,ruff]"]
dev = ["tagstudio[mkdocs,mypy,pre-commit,pyinstaller,pytest,ruff]"]
mkdocs = ["mkdocs-material[imaging]>=9.6.14", "mkdocs-redirects~=1.2"]
pyright = ["pyright~=1.1.409"]
mypy = ["mypy==1.15.0", "mypy-extensions==1.*", "types-ujson~=5.10"]
pre-commit = ["pre-commit~=4.2"]
pyinstaller = ["Pyinstaller~=6.13"]
pytest = [
"pytest==9.0.3",
"pytest==8.3.5",
"pytest-cov==6.1.1",
"pytest-mock==3.15.1",
"pytest-qt==4.4.0",
"syrupy==5.1.0",
"syrupy==4.9.1",
]
ruff = ["ruff==0.11.8"]
@@ -62,6 +62,31 @@ tagstudio = "tagstudio.main:main"
[tool.hatch.build.targets.wheel]
packages = ["src/tagstudio"]
[tool.mypy]
mypy_path = ["src/tagstudio"]
disable_error_code = [
"annotation-unchecked",
"func-returns-value",
"import-untyped",
]
explicit_package_bases = true
ignore_missing_imports = true
implicit_optional = true
strict_optional = false
warn_unused_ignores = true
exclude = ["build", "dist"]
[[tool.mypy.overrides]]
module = "tagstudio.qt.main_window"
ignore_errors = true
[[tool.mypy.overrides]]
module = "tagstudio.qt.ui.home_ui"
ignore_errors = true
[[tool.mypy.overrides]]
module = "tagstudio.core.ts_core"
ignore_errors = true
[tool.pytest.ini_options]
#addopts = "-m 'not qt'"
@@ -74,20 +99,17 @@ ignore = [
"src/tagstudio/qt/previews/vendored/pydub/",
]
include = ["src/tagstudio", "tests"]
# reference for the settings here: https://github.com/microsoft/pyright/blob/main/docs/configuration.md
reportAny = false
reportIgnoreCommentWithoutRule = false
reportImplicitStringConcatenation = false
reportImportCycles = false
reportMissingTypeArgument = false
reportMissingTypeStubs = false
# reportOptionalMemberAccess = false
reportUnannotatedClassAttribute = false
reportUnknownArgumentType = false
reportUnknownLambdaType = false
reportUnknownMemberType = false
reportUnusedCallResult = false
reportUnannotatedClassAttribute = false
reportUninitializedInstanceVariable = false
[tool.ruff]
exclude = ["home_ui.py", "resources.py", "resources_rc.py"]
@@ -9,7 +9,7 @@ JSON_FILENAME: str = "ts_library.json"
DB_VERSION_CURRENT_KEY: str = "CURRENT"
DB_VERSION_INITIAL_KEY: str = "INITIAL"
DB_VERSION: int = 201
DB_VERSION: int = 200
TAG_CHILDREN_QUERY = text("""
WITH RECURSIVE ChildTags AS (
+6 -6
View File
@@ -20,19 +20,19 @@ class BaseField(Base):
@declared_attr
def id(self) -> Mapped[int]:
return mapped_column(primary_key=True, autoincrement=True, sort_order=1)
return mapped_column(primary_key=True, autoincrement=True)
@declared_attr
def name(self) -> Mapped[str]:
return mapped_column(nullable=False, default="", sort_order=2)
return mapped_column(nullable=False, default="")
@declared_attr
def entry_id(self) -> Mapped[int]:
return mapped_column(ForeignKey("entries.id"), sort_order=3)
return mapped_column(ForeignKey("entries.id"))
@declared_attr
def entry(self) -> Mapped[Entry]:
return relationship(foreign_keys=[self.entry_id]) # pyright: ignore[reportArgumentType]
return relationship(foreign_keys=[self.entry_id]) # type: ignore # pyright: ignore[reportArgumentType]
@property
def class_name(self) -> str:
@@ -47,7 +47,7 @@ class BaseField(Base):
class TextField(BaseField):
__tablename__ = "text_fields"
value: Mapped[str | None] = mapped_column(sort_order=4)
value: Mapped[str | None]
is_multiline: Mapped[bool] = mapped_column(nullable=False, default=False)
@override
@@ -75,7 +75,7 @@ class TextField(BaseField):
class DatetimeField(BaseField):
__tablename__ = "datetime_fields"
value: Mapped[str | None] = mapped_column(sort_order=4)
value: Mapped[str | None]
@override
def __eq__(self, other: object) -> bool:
+7 -60
View File
@@ -419,7 +419,6 @@ class Library:
# Under -> sqlite-the-sqlite-dialect-now-uses-nullpool-for-file-based-databases
poolclass = None if storage_path == ":memory:" else NullPool
loaded_db_version: int = 0
initial_db_version: int = DB_VERSION
logger.info(
"[Library] Opening SQLite Library",
@@ -431,7 +430,6 @@ class Library:
# Don't check DB version when creating new library
if not is_new:
loaded_db_version = self.get_version(DB_VERSION_CURRENT_KEY)
initial_db_version = self.get_version(DB_VERSION_INITIAL_KEY)
# ======================== Library Database Version Checking =======================
# DB_VERSION 6 is the first supported SQLite DB version.
@@ -454,7 +452,7 @@ class Library:
),
)
logger.info(f"[Library] Library DB version: {loaded_db_version}")
logger.info(f"[Library] DB_VERSION: {loaded_db_version}")
make_tables(self.engine)
if is_new:
@@ -573,9 +571,6 @@ class Library:
self.__apply_db104_migrations(session, library_dir)
if loaded_db_version < 200:
self.__apply_db200_migrations(session)
# changes: field tables
if initial_db_version < 200 and loaded_db_version < 201:
self.__apply_db201_migrations(session)
session.execute(
text("CREATE INDEX IF NOT EXISTS idx_tags_name_shorthand ON tags (name, shorthand)")
@@ -593,7 +588,6 @@ class Library:
# Update DB_VERSION
if loaded_db_version < DB_VERSION:
logger.info(f"[Library] Library migrated to DB version {DB_VERSION}")
self.set_version(DB_VERSION_CURRENT_KEY, DB_VERSION)
# everything is fine, set the library path
@@ -814,6 +808,10 @@ class Library:
session.execute(text("UPDATE datetime_fields SET name = type_key"))
session.flush()
# TODO: Remove `type_key` columns from text_fields and datetime_fields tables.
# See issue with dropping columns foreign keys in SQLite:
# https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes
# Change `name` values to title case
logger.info("[Library][Migration][200] Normalizing TextField names...")
for text_field in session.execute(select(TextField)).scalars():
@@ -865,57 +863,6 @@ class Library:
session.commit()
def __apply_db201_migrations(self, session: Session):
"""Migrate DB to DB_VERSION 201."""
with session:
create_text_fields_table = text("""
CREATE TABLE text_fields_new (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name VARCHAR NOT NULL,
entry_id INTEGER NOT NULL,
value VARCHAR,
is_multiline BOOLEAN NOT NULL,
FOREIGN KEY(entry_id) REFERENCES entries (id)
)
""")
create_datetime_fields_table = text("""
CREATE TABLE datetime_fields_new (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name VARCHAR NOT NULL,
entry_id INTEGER NOT NULL,
value VARCHAR,
FOREIGN KEY(entry_id) REFERENCES entries (id)
)
""")
logger.info("[Library][Migration][201] Dropping type_key from text_fields table...")
session.execute(create_text_fields_table)
session.flush()
session.execute(
text("""
INSERT INTO text_fields_new (id, name, entry_id, value, is_multiline)
SELECT id, name, entry_id, value, is_multiline
FROM text_fields
""")
)
session.execute(text("DROP TABLE text_fields"))
session.execute(text("ALTER TABLE text_fields_new RENAME TO text_fields"))
logger.info("[Library][Migration][201] Dropping type_key from datetime_fields table...")
session.execute(create_datetime_fields_table)
session.flush()
session.execute(
text("""
INSERT INTO datetime_fields_new (id, name, entry_id, value)
SELECT id, name, entry_id, value
FROM datetime_fields
""")
)
session.execute(text("DROP TABLE datetime_fields"))
session.execute(text("ALTER TABLE datetime_fields_new RENAME TO datetime_fields"))
session.commit()
@property
def field_templates(self) -> Sequence[BaseFieldTemplate]:
with Session(self.engine) as session:
@@ -1010,7 +957,7 @@ class Library:
)
statement = statement.distinct()
entries: ScalarResult[Entry] | list[Entry] = session.execute(statement).scalars()
entries = entries.unique()
entries = entries.unique() # type: ignore
entry_order_dict = {e_id: order for order, e_id in enumerate(entry_ids)}
entries = sorted(entries, key=lambda e: entry_order_dict[e.id])
@@ -1467,7 +1414,7 @@ class Library:
if tag:
tags.append(tag.id)
else:
new = session.add(Tag(name=string))
new = session.add(Tag(name=string)) # type: ignore
if new:
tags.append(new.id)
session.flush()
@@ -47,21 +47,21 @@ class SQLBoolExpressionBuilder(BaseVisitor[ColumnElement[bool]]):
self.lib = lib
@override
def visit_or_list(self, node: ORList) -> ColumnElement[bool]:
def visit_or_list(self, node: ORList) -> ColumnElement[bool]: # type: ignore
tag_ids, bool_expressions = self.__separate_tags(node.elements, only_single=False)
if len(tag_ids) > 0:
bool_expressions.append(self.__entry_has_any_tags(tag_ids))
return or_(*bool_expressions)
@override
def visit_and_list(self, node: ANDList) -> ColumnElement[bool]:
def visit_and_list(self, node: ANDList) -> ColumnElement[bool]: # type: ignore
tag_ids, bool_expressions = self.__separate_tags(node.terms, only_single=True)
if len(tag_ids) > 0:
bool_expressions.append(self.__entry_has_all_tags(tag_ids))
return and_(*bool_expressions)
@override
def visit_constraint(self, node: Constraint) -> ColumnElement[bool]:
def visit_constraint(self, node: Constraint) -> ColumnElement[bool]: # type: ignore
"""Returns a Boolean Expression that is true, if the Entry satisfies the constraint."""
if len(node.properties) != 0:
raise NotImplementedError("Properties are not implemented yet") # TODO TSQLANG
@@ -113,11 +113,11 @@ class SQLBoolExpressionBuilder(BaseVisitor[ColumnElement[bool]]):
raise NotImplementedError("This type of constraint is not implemented yet")
@override
def visit_property(self, node: Property) -> ColumnElement[bool]:
def visit_property(self, node: Property) -> ColumnElement[bool]: # type: ignore
raise NotImplementedError("This should never be reached!")
@override
def visit_not(self, node: Not) -> ColumnElement[bool]:
def visit_not(self, node: Not) -> ColumnElement[bool]: # type: ignore
return ~self.visit(node.child)
def __get_tag_ids(self, tag_name: str, include_children: bool = True) -> list[int]:
@@ -88,6 +88,6 @@ class FixIgnoredEntriesModal(FixIgnoredEntriesModalView):
self.driver.library_info_window.update_cleanup()
@override
def showEvent(self, event: QtGui.QShowEvent) -> None:
def showEvent(self, event: QtGui.QShowEvent) -> None: # type: ignore
self.update_ignored_count()
return super().showEvent(event)
@@ -46,6 +46,6 @@ class IgnoreModal(IgnoreModalView):
Ignore.write_ignore_file(self.lib.library_dir, lines)
@override
def showEvent(self, event: QShowEvent) -> None:
def showEvent(self, event: QShowEvent) -> None: # type: ignore
self.__load_file()
return super().showEvent(event)
@@ -162,6 +162,6 @@ class LibraryInfoWindow(LibraryInfoWindowView):
return size
@override
def showEvent(self, event: QtGui.QShowEvent):
def showEvent(self, event: QtGui.QShowEvent): # type: ignore
self.refresh()
return super().showEvent(event)
@@ -4,7 +4,7 @@
import io
from pathlib import Path
from typing import TYPE_CHECKING, override
from typing import TYPE_CHECKING
import cv2
import rawpy
@@ -12,10 +12,6 @@ import structlog
from PIL import Image, UnidentifiedImageError
from PIL.Image import DecompressionBombError
from PySide6.QtCore import QSize
from rawpy import (
LibRawFileUnsupportedError, # pyright: ignore[reportPrivateImportUsage]
LibRawIOError, # pyright: ignore[reportPrivateImportUsage]
)
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.media_types import MediaCategories
@@ -54,8 +50,8 @@ class PreviewThumb(PreviewThumbView):
stats.width = image.width
stats.height = image.height
except (
LibRawIOError,
LibRawFileUnsupportedError,
rawpy.LibRawIOError,
rawpy.LibRawFileUnsupportedError,
FileNotFoundError,
):
pass
@@ -148,22 +144,18 @@ class PreviewThumb(PreviewThumbView):
self._display_image(filepath)
return self.__get_image_stats(filepath)
@override
def _open_file_action_callback(self):
open_file(
self.__current_file, windows_start_command=self.__driver.settings.windows_start_command
)
@override
def _open_explorer_action_callback(self):
open_file(self.__current_file, file_manager=True)
@override
def _delete_action_callback(self):
if bool(self.__current_file):
self.__driver.delete_files_callback(self.__current_file)
@override
def _button_wrapper_callback(self):
open_file(
self.__current_file, windows_start_command=self.__driver.settings.windows_start_command
@@ -34,7 +34,7 @@ class TagBoxWidget(TagBoxWidgetView):
self.__entries = entries
@override
def _on_click(self, tag: Tag) -> None:
def _on_click(self, tag: Tag) -> None: # type: ignore[misc]
match self.__driver.settings.tag_click_action:
case TagClickActionOption.OPEN_EDIT:
self._on_edit(tag)
@@ -58,7 +58,7 @@ class TagBoxWidget(TagBoxWidgetView):
)
@override
def _on_remove(self, tag: Tag) -> None:
def _on_remove(self, tag: Tag) -> None: # type: ignore[misc]
logger.info(
"[TagBoxWidget] remove_tag",
selected=self.__entries,
@@ -70,7 +70,7 @@ class TagBoxWidget(TagBoxWidgetView):
self.on_update.emit()
@override
def _on_edit(self, tag: Tag) -> None:
def _on_edit(self, tag: Tag) -> None: # type: ignore[misc]
build_tag_panel = BuildTagPanel(self.__driver.lib, tag=tag)
edit_modal = PanelModal(
@@ -92,7 +92,7 @@ class TagBoxWidget(TagBoxWidgetView):
edit_modal.show()
@override
def _on_search(self, tag: Tag) -> None:
def _on_search(self, tag: Tag) -> None: # type: ignore[misc]
self.__driver.main_window.search_field.setText(f"tag_id:{tag.id}")
self.__driver.update_browsing_state(
BrowsingState.from_tag_id(tag.id, self.__driver.browsing_history.current)
+2 -2
View File
@@ -7,7 +7,7 @@ from PIL import Image, ImageDraw, ImageFont
def wrap_line(
text: str,
font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
font: ImageFont.ImageFont,
width: int = 256,
draw: ImageDraw.ImageDraw | None = None,
) -> int:
@@ -31,7 +31,7 @@ def wrap_line(
def wrap_full_text(
text: str,
font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
font: ImageFont.ImageFont,
width: int = 256,
draw: ImageDraw.ImageDraw | None = None,
) -> str:
+1 -1
View File
@@ -138,7 +138,7 @@ class ColorBoxWidget(FieldWidget):
)
self.edit_modal.saved.connect(
lambda: (self.lib.update_color(*build_color_panel.build_color()), self.updated.emit())
lambda: (self.lib.update_color(*build_color_panel.build_color()), self.updated.emit()) # type: ignore
)
self.edit_modal.show()
@@ -114,7 +114,6 @@ class FileAttributes(QWidget):
if filepath and filepath.is_file():
created: dt
if platform.system() == "Windows" or platform.system() == "Darwin":
# NOTE: Accessing stat().st_birthtime causes linter checks to fail on some systems.
created = dt.fromtimestamp(filepath.stat().st_birthtime) # type: ignore[attr-defined, unused-ignore]
else:
created = dt.fromtimestamp(filepath.stat().st_ctime)
+3 -3
View File
@@ -467,12 +467,12 @@ class ItemThumb(FlowWidget):
badge.setHidden(is_hidden)
@override
def enterEvent(self, event: QEnterEvent) -> None:
def enterEvent(self, event: QEnterEvent) -> None: # type: ignore[misc]
self.show_check_badges(show=True)
return super().enterEvent(event)
@override
def leaveEvent(self, event: QEvent) -> None:
def leaveEvent(self, event: QEvent) -> None: # type: ignore[misc]
self.show_check_badges(show=False)
return super().leaveEvent(event)
@@ -502,7 +502,7 @@ class ItemThumb(FlowWidget):
)
@override
def mouseMoveEvent(self, event: QMouseEvent) -> None:
def mouseMoveEvent(self, event: QMouseEvent) -> None: # type: ignore[misc]
if event.buttons() is not Qt.MouseButton.LeftButton:
return
+1 -1
View File
@@ -324,7 +324,7 @@ class MediaPlayer(QGraphicsView):
"""Manage events for the media player."""
if (
arg__2.type() == QEvent.Type.MouseButtonPress
and arg__2.button() == Qt.MouseButton.LeftButton # pyright: ignore[reportAttributeAccessIssue]
and arg__2.button() == Qt.MouseButton.LeftButton # type: ignore
):
if arg__1 == self.play_pause:
self.toggle_play()
+11 -11
View File
@@ -364,7 +364,7 @@ class JsonMigrationModal(QObject):
iterator = FunctionIterator(self.migration_iterator)
iterator.value.connect(
lambda x: (
pb.setLabelText(f"<h4>{x}</h4>"),
pb.setLabelText(f"<h4>{x}</h4>"), # type: ignore
self.update_sql_value_ui(show_msg_box=False)
if x == Translations["json_migration.checking_for_parity"]
else (),
@@ -377,8 +377,8 @@ class JsonMigrationModal(QObject):
r.done.connect(
lambda: (
self.update_sql_value_ui(show_msg_box=not skip_ui),
pb.setMinimum(1),
pb.setValue(1),
pb.setMinimum(1), # type: ignore
pb.setValue(1), # type: ignore
# Enable the finish button
cast(QPushButtonWrapper, self.stack[1].buttons[4]).setDisabled(False),
)
@@ -483,26 +483,26 @@ class JsonMigrationModal(QObject):
def update_json_entry_count(self, value: int):
self.old_entry_count = value
label: QLabel = self.old_content_layout.itemAtPosition(self.entries_row, 1).widget() # pyright: ignore[reportAssignmentType]
label: QLabel = self.old_content_layout.itemAtPosition(self.entries_row, 1).widget() # type:ignore
label.setText(self.color_value_default(value))
def update_json_tag_count(self, value: int):
self.old_tag_count = value
label: QLabel = self.old_content_layout.itemAtPosition(self.tags_row, 1).widget() # pyright: ignore[reportAssignmentType]
label: QLabel = self.old_content_layout.itemAtPosition(self.tags_row, 1).widget() # type:ignore
label.setText(self.color_value_default(value))
def update_sql_value(self, row: int, value: int | bool, old_value: int | bool):
label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore[reportAssignmentType]
warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore[reportAssignmentType]
label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # type:ignore
warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # type:ignore
label.setText(self.color_value_conditional(old_value, value))
warning_icon.setText("" if old_value == value else self.warning)
def update_parity_value(self, row: int, value: bool):
result: str = self.match_text if value else self.differ_text
old_label: QLabel = self.old_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore[reportAssignmentType]
new_label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore[reportAssignmentType]
old_warning_icon: QLabel = self.old_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore[reportAssignmentType]
new_warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore[reportAssignmentType]
old_label: QLabel = self.old_content_layout.itemAtPosition(row, 1).widget() # type:ignore
new_label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # type:ignore
old_warning_icon: QLabel = self.old_content_layout.itemAtPosition(row, 2).widget() # type:ignore
new_warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # type:ignore
old_label.setText(self.color_value_conditional(self.match_text, result))
new_label.setText(self.color_value_conditional(self.match_text, result))
old_warning_icon.setText("" if value else self.warning)
+4 -2
View File
@@ -218,7 +218,7 @@ class Pagination(QWidget):
if i < index:
if (i != 0) and i >= index - 4:
self.start_buffer_layout.itemAt(i - start_offset).widget().setHidden(False)
self.start_buffer_layout.itemAt(i - start_offset).widget().setText( # pyright: ignore[reportAttributeAccessIssue]
self.start_buffer_layout.itemAt(i - start_offset).widget().setText( # type: ignore
str(i + 1)
)
self._assign_click(
@@ -237,7 +237,9 @@ class Pagination(QWidget):
elif i > index:
if i != page_count - 1 and i <= index + 4:
self.end_buffer_layout.itemAt(i - end_offset).widget().setHidden(False)
self.end_buffer_layout.itemAt(i - end_offset).widget().setText(str(i + 1)) # pyright: ignore[reportAttributeAccessIssue]
self.end_buffer_layout.itemAt(i - end_offset).widget().setText( # type: ignore
str(i + 1)
)
self._assign_click(
cast(
QPushButtonWrapper,
+1 -1
View File
@@ -62,6 +62,6 @@ class ProgressWidget(QWidget):
r = CustomRunnable(lambda: iterator.run())
r.done.connect(
lambda: (self.hide(), self.deleteLater(), [callback() for callback in done_callbacks])
lambda: (self.hide(), self.deleteLater(), [callback() for callback in done_callbacks]) # type: ignore
)
QThreadPool.globalInstance().start(r)
+3 -3
View File
@@ -59,15 +59,15 @@ class TagDatabasePanel(TagSearchPanel):
return
message_box = QMessageBox(
QMessageBox.Question, # pyright: ignore[reportAttributeAccessIssue]
QMessageBox.Question, # type: ignore
Translations["tag.remove"],
Translations.format("tag.confirm_delete", tag_name=self.lib.tag_display_name(tag)),
QMessageBox.Ok | QMessageBox.Cancel, # pyright: ignore[reportAttributeAccessIssue]
QMessageBox.Ok | QMessageBox.Cancel, # type: ignore
)
result = message_box.exec()
if result != QMessageBox.Ok: # pyright: ignore[reportAttributeAccessIssue]
if result != QMessageBox.Ok: # type: ignore
return
self.lib.remove_tag(tag.id)
+26 -35
View File
@@ -43,7 +43,7 @@ from PIL import (
UnidentifiedImageError,
)
from PIL.Image import DecompressionBombError
from pillow_heif import register_heif_opener # pyright: ignore[reportUnknownVariableType]
from pillow_heif import register_heif_opener
from PySide6.QtCore import (
QBuffer,
QFile,
@@ -58,10 +58,6 @@ from PySide6.QtCore import (
from PySide6.QtGui import QGuiApplication, QImage, QPainter, QPixmap
from PySide6.QtPdf import QPdfDocument, QPdfDocumentRenderOptions
from PySide6.QtSvg import QSvgRenderer
from rawpy import (
LibRawFileUnsupportedError, # pyright: ignore[reportPrivateImportUsage]
LibRawIOError, # pyright: ignore[reportPrivateImportUsage]
)
from tagstudio.core.constants import (
FONT_SAMPLE_SIZES,
@@ -79,11 +75,9 @@ from tagstudio.qt.helpers.gradients import four_corner_gradient
from tagstudio.qt.helpers.image_effects import replace_transparent_pixels
from tagstudio.qt.helpers.text_wrapper import wrap_full_text
from tagstudio.qt.models.palette import UI_COLORS, ColorType, UiColor, get_ui_color
from tagstudio.qt.previews.vendored.blender_renderer import (
blend_thumb, # pyright: ignore[reportUnknownVariableType]
)
from tagstudio.qt.previews.vendored.blender_renderer import blend_thumb
from tagstudio.qt.previews.vendored.pydub.audio_segment import (
_AudioSegment as AudioSegment, # pyright: ignore[reportPrivateUsage]
_AudioSegment as AudioSegment,
)
from tagstudio.qt.resource_manager import ResourceManager
@@ -124,7 +118,7 @@ class _TarFile:
def __init__(self, filepath: Path, mode: Literal["r"]) -> None:
self.tar: tarfile.TarFile
self.filepath = filepath
self.mode: Literal["r"] = mode
self.mode = mode
def namelist(self) -> list[str]:
return self.tar.getnames()
@@ -133,10 +127,10 @@ class _TarFile:
return unwrap(self.tar.extractfile(name)).read()
def __enter__(self) -> "_TarFile":
self.tar = tarfile.open(name=self.filepath, mode=self.mode).__enter__()
self.tar = tarfile.open(self.filepath, self.mode).__enter__()
return self
def __exit__(self, *args) -> None: # pyright: ignore[reportUnknownParameterType, reportMissingParameterType]
def __exit__(self, *args) -> None:
self.tar.__exit__(*args)
@@ -299,7 +293,7 @@ class ThumbRenderer(QObject):
im: Image.Image = Image.new(
mode="L",
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # type: ignore
color="black",
)
draw = ImageDraw.Draw(im)
@@ -330,7 +324,7 @@ class ThumbRenderer(QObject):
# Highlight
im_hl: Image.Image = Image.new(
mode="RGBA",
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # type: ignore
color="#00000000",
)
draw = ImageDraw.Draw(im_hl)
@@ -349,7 +343,7 @@ class ThumbRenderer(QObject):
# Shadow
im_sh: Image.Image = Image.new(
mode="RGBA",
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # type: ignore
color="#00000000",
)
draw = ImageDraw.Draw(im_sh)
@@ -394,7 +388,7 @@ class ThumbRenderer(QObject):
# Create larger blank image based on smooth_factor
im: Image.Image = Image.new(
"RGBA",
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # type: ignore
color="#FF000000",
)
@@ -402,13 +396,13 @@ class ThumbRenderer(QObject):
bg: Image.Image
bg = Image.new(
"RGB",
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # type: ignore
color="#000000FF",
)
# Use a background image if provided
if bg_image:
bg_im = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # pyright: ignore[reportArgumentType]
bg_im = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore
bg_im = ImageEnhance.Brightness(bg_im).enhance(0.3) # Reduce the brightness
bg.paste(bg_im)
@@ -417,7 +411,7 @@ class ThumbRenderer(QObject):
bg,
(0, 0),
mask=self._get_mask(
tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
tuple([d * smooth_factor for d in size]), # type: ignore
(pixel_ratio * smooth_factor),
),
)
@@ -501,19 +495,19 @@ class ThumbRenderer(QObject):
# Create larger blank image based on smooth_factor
im: Image.Image = Image.new(
"RGBA",
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # type: ignore
color="#00000000",
)
bg: Image.Image
# Use a background image if provided
if bg_image:
bg = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # pyright: ignore[reportArgumentType]
bg = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore
# Create solid background color
else:
bg = Image.new(
"RGB",
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # type: ignore
color="#000000",
)
# Apply color overlay
@@ -527,7 +521,7 @@ class ThumbRenderer(QObject):
bg,
(0, 0),
mask=self._get_mask(
tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
tuple([d * smooth_factor for d in size]), # type: ignore
(pixel_ratio * smooth_factor),
),
)
@@ -668,17 +662,17 @@ class ThumbRenderer(QObject):
artwork = None
if ext in [".mp3"]:
id3_tags: id3.ID3 = id3.ID3(filepath)
id3_covers: list = id3_tags.getall("APIC") # pyright: ignore[reportUnknownVariableType]
id3_covers: list = id3_tags.getall("APIC")
if id3_covers:
artwork = Image.open(BytesIO(id3_covers[0].data))
elif ext in [".flac"]:
flac_tags: flac.FLAC = flac.FLAC(filepath)
flac_covers: list = flac_tags.pictures # pyright: ignore[reportUnknownVariableType]
flac_covers: list = flac_tags.pictures
if flac_covers:
artwork = Image.open(BytesIO(flac_covers[0].data))
elif ext in [".mp4", ".m4a", ".aac"]:
mp4_tags: mp4.MP4 = mp4.MP4(filepath)
mp4_covers: list | None = mp4_tags.get("covr") # pyright: ignore[reportUnknownVariableType]
mp4_covers: list | None = mp4_tags.get("covr") # pyright: ignore[reportAssignmentType]
if mp4_covers:
artwork = Image.open(BytesIO(mp4_covers[0]))
if artwork:
@@ -1094,7 +1088,7 @@ class ThumbRenderer(QObject):
font = ImageFont.truetype(filepath, size=font_size)
text_wrapped: str = wrap_full_text(
FONT_SAMPLE_TEXT,
font=font,
font=font, # pyright: ignore[reportArgumentType]
width=size,
draw=draw,
)
@@ -1126,8 +1120,8 @@ class ThumbRenderer(QObject):
)
except (
DecompressionBombError,
LibRawIOError,
LibRawFileUnsupportedError,
rawpy.LibRawIOError,
rawpy.LibRawFileUnsupportedError,
) as e:
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
return im
@@ -1143,7 +1137,6 @@ class ThumbRenderer(QObject):
try:
# Load the EXR data to an array and rotate the color space from BGRA -> RGBA
raw_array = cv2.imread(str(filepath), cv2.IMREAD_UNCHANGED)
assert raw_array is not None
raw_array[..., :3] = raw_array[..., 2::-1]
# Correct the gamma of the raw array
@@ -1216,7 +1209,7 @@ class ThumbRenderer(QObject):
# Write the image to a buffer as png
buffer: QBuffer = QBuffer()
buffer.open(QBuffer.OpenModeFlag.ReadWrite)
q_image.save(device=buffer, format="PNG") # pyright: ignore[reportArgumentType]
q_image.save(buffer, "PNG") # type: ignore[call-overload]
# Load the image from the buffer
im = Image.new("RGB", (size, size), color="#1e1e1e")
@@ -1265,7 +1258,7 @@ class ThumbRenderer(QObject):
return im
@staticmethod
def _model_stl_thumb(filepath: Path, size: int) -> Image.Image | None: # pyright: ignore[reportUnusedParameter]
def _model_stl_thumb(filepath: Path, size: int) -> Image.Image | None:
"""Render a thumbnail for an STL file.
Args:
@@ -1338,7 +1331,7 @@ class ThumbRenderer(QObject):
buffer: QBuffer = QBuffer()
buffer.open(QBuffer.OpenModeFlag.ReadWrite)
try:
q_image.save(buffer, "PNG") # pyright: ignore
q_image.save(buffer, "PNG") # type: ignore # pyright: ignore
im = Image.open(BytesIO(buffer.buffer().data()))
finally:
buffer.close()
@@ -1621,7 +1614,6 @@ class ThumbRenderer(QObject):
def fetch_cached_image(file_name: Path):
image: Image.Image | None = None
assert self.driver.cache_manager is not None
cached_path = self.driver.cache_manager.get_file_path(file_name)
if cached_path and cached_path.is_file():
@@ -1884,7 +1876,6 @@ class ThumbRenderer(QObject):
image = self._resize_image(image, (adj_size, adj_size))
if save_to_file and savable_media_type and image:
assert self.driver.cache_manager is not None
self.driver.cache_manager.save_image(image, save_to_file, mode="RGBA")
except (
@@ -2,6 +2,7 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
# Vendored from pydub
# type: ignore
import array
+13 -12
View File
@@ -8,6 +8,7 @@
"""A Qt driver for TagStudio."""
import contextlib
import ctypes
import math
import os
@@ -193,7 +194,7 @@ class QtDriver(DriverMixin, QObject):
applied_theme: Theme
lib: Library
cache_manager: CacheManager | None
cache_manager: CacheManager
browsing_history: History[BrowsingState]
@@ -350,7 +351,7 @@ class QtDriver(DriverMixin, QObject):
if os.name == "nt":
appid = "cyanvoxel.tagstudio.9"
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid)
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid) # type: ignore[attr-defined,unused-ignore]
self.app.setApplicationName("tagstudio")
self.app.setApplicationDisplayName("TagStudio")
@@ -538,7 +539,7 @@ class QtDriver(DriverMixin, QObject):
# TODO: Move this to a settings screen.
self.main_window.menu_bar.clear_thumb_cache_action.triggered.connect(
lambda: unwrap(self.cache_manager).clear_cache()
lambda: self.cache_manager.clear_cache()
)
# endregion
@@ -912,7 +913,7 @@ class QtDriver(DriverMixin, QObject):
origin_id(id): The entry ID associated with the widget making the call.
"""
entry: Entry | None = None
pending: list[tuple[int | None, Path]] = []
pending: list[tuple[int, Path]] = []
deleted_count: int = 0
selected = self.selected
@@ -920,13 +921,14 @@ class QtDriver(DriverMixin, QObject):
if len(selected) <= 1 and origin_path:
origin_id_ = origin_id
if origin_id_ is None:
origin_id_ = selected[0] if len(selected) > 0 else None
if not origin_id_:
with contextlib.suppress(IndexError):
origin_id_ = selected[0]
pending.append((origin_id_, Path(origin_path)))
else:
for item in selected:
entry = unwrap(self.lib.get_entry(item))
entry = self.lib.get_entry(item)
filepath: Path = entry.path
pending.append((item, filepath))
@@ -948,8 +950,7 @@ class QtDriver(DriverMixin, QObject):
self.main_window.status_bar.showMessage(msg)
self.main_window.status_bar.repaint()
if e_id is not None:
self.lib.remove_entries([e_id])
self.lib.remove_entries([e_id])
if delete_file(library_dir / f):
deleted_count += 1
@@ -1100,7 +1101,7 @@ class QtDriver(DriverMixin, QObject):
pw.hide(),
pw.deleteLater(),
# refresh the library only when new items are added
files_count and self.update_browsing_state(),
files_count and self.update_browsing_state(), # type: ignore
)
)
QThreadPool.globalInstance().start(r)
@@ -1225,10 +1226,10 @@ class QtDriver(DriverMixin, QObject):
for field in self.copy_buffer["fields"]:
exists = False
for e in existing_fields:
if field == e:
if field.type_key == e.type_key and field.value == e.value:
exists = True
if not exists:
self.lib.add_field_to_entries(id, field=field)
self.lib.add_field_to_entries(id, field_id=field.type_key, value=field.value)
self.lib.add_tags_to_entries(id, self.copy_buffer["tags"])
if len(self.selected) > 1:
if TAG_ARCHIVED in self.copy_buffer["tags"]:
+1 -1
View File
@@ -5,7 +5,7 @@
from PySide6.QtCore import QObject, QRunnable, Signal
class CustomRunnable(QRunnable, QObject): # pyright: ignore[reportUnsafeMultipleInheritance]
class CustomRunnable(QRunnable, QObject):
done = Signal()
def __init__(self, function) -> None:
+3 -3
View File
@@ -86,7 +86,7 @@ class ThumbButton(QPushButtonWrapper):
)
@override
def paintEvent(self, arg__1: QPaintEvent) -> None:
def paintEvent(self, arg__1: QPaintEvent) -> None: # type: ignore
super().paintEvent(arg__1)
if self.hovered or self.selected:
painter = QPainter()
@@ -127,13 +127,13 @@ class ThumbButton(QPushButtonWrapper):
painter.end()
@override
def enterEvent(self, event: QEnterEvent) -> None:
def enterEvent(self, event: QEnterEvent) -> None: # type: ignore
self.hovered = True
self.repaint()
return super().enterEvent(event)
@override
def leaveEvent(self, event: QEvent) -> None:
def leaveEvent(self, event: QEvent) -> None: # type: ignore
self.hovered = False
self.repaint()
return super().leaveEvent(event)
+36 -70
View File
@@ -1,6 +1,6 @@
{
"about.config_path": "Mlafuplas fu sentakuzma",
"about.description": "TagStudio - apli per parjat mlafu au riso mit festaretol, hadafsui per anta sentakuzma au mange dekizma brukdjin made. Nil kinijena apli os mlafufal, nil mare fu flankamlafu, au na nilraz ti kawri hel parjat fu kompyu.",
"about.description": "TagStudio zeting ke parjat mlafu au riso mit festaretol, hadafvui per anta sentakuzma au mange dekizma brukdjin made. Nil kinijena zeting os mlafufal, jamnai mare fu flanka mlafu au perpa tumam fu kompyu.",
"about.documentation": "Mahaklarazma",
"about.license": "Brukruuru",
"about.module.found": "Finnajena",
@@ -10,11 +10,11 @@
"app.pre_release": "De-Gvir",
"app.title": "{base_title} - Mlafuhuomi '{library_dir}'",
"color.color_border": "Bruk minusvikti varge per flanka",
"color.confirm_delete": "Du kestetsa varge \"{color_name}\" we?",
"color.confirm_delete": "Du kestetsa varge \"{color_name}\"?",
"color.delete": "Keste festaretol",
"color.import_pack": "Nasii klaani fu varge",
"color.name": "Namae",
"color.namespace.delete.prompt": "Du kestetsa afto vargetumam we? Afto zol keste ALTING ine vargetumam au sebja!",
"color.namespace.delete.prompt": "Du kestetsa afto vargetumam? Afto zol keste alting ine vargetumam au sebja!",
"color.namespace.delete.title": "Keste vargetumam",
"color.new": "Neo varge",
"color.placeholder": "Varge",
@@ -36,61 +36,47 @@
"edit.copy_fields": "Mverm shiruzmafal",
"edit.paste_fields": "Nasii shiru",
"edit.tag_manager": "Jewalt festaretol",
"entries.duplicate.merge": "Visk blisnets shiruzmakaban",
"entries.duplicate.merge.label": "Visk blisnets shiruzmakaban ima...",
"entries.duplicate.refresh": "Gotova blisnets shiruzmakaban gen",
"entries.duplicates.description": "Blisnets shiruzmakaban implajena na plus ka ein shiruzmakaban ke tsunaga na sama mlafu na shiruzmabaksu. Na visk afto, zol festa festaretol au mlafushiruzma al mverm fu mlafu kara ine ein stuur shiruzmakaban. Hej nai sama \"mverm fu mlafu\", ke mverm fu mlafu fu du, ekso TagStudio.",
"entries.generic.refresh_alt": "&Gengotova",
"entries.duplicate.merge": "Visk sama shiruzmakaban",
"entries.duplicate.merge.label": "Visk sama shiruzmakaban ima...",
"entries.duplicate.refresh": "Gotova sama shiruzmakaban gen",
"entries.duplicates.description": "Mverm fu shiruzmakaban implajena na plus ka ein shiruzmakaban ke tsunaga na sama mlafu na shiruzmabaksu. Na visk afto, zol festa festaretol au mlafushiruzma al mverm fu mlafu kara ine ein stuur shiruzmakaban. Hej nai sama \"mverm fu mlafu\", ke mverm fu mlafu fu du, ekso TagStudio.",
"entries.generic.remove.removing": "Keste shiruzmakaban ima",
"entries.generic.remove.removing_count": "Keste {count} shiruzmakaban...",
"entries.ignored.description": "Mlafu shiruzmakaban bli \"hestujena\" li antadan na mlafuhuomi de hesturuuru fu brukdjin (na '.ts_ignore' mlafu) kawaridan per nai eku sore. Hestujena mlafu long mlafuhuomi na snano sit kemuske mlafu na lasazma kesa kawari hesturuuru.",
"entries.ignored.ignored_count": "Hestujena Shiruzmakaban: {count}",
"entries.ignored.remove": "Keste hestujena shiruzmakaban",
"entries.ignored.remove_alt": "Keste hestujena shiruzmakaban (&v)",
"entries.ignored.scanning": "Taskame mlafuhuomi grun hestujena shiruzmakaban...",
"entries.ignored.title": "Fiks hestujena shiruzmakaban",
"entries.mirror": "&Maha melon fu",
"entries.mirror.confirmation": "Du mahatsa melon fu afto {count} shiruzmakaban we?",
"entries.mirror.confirmation": "Du mahatsa melon fu afto {count} shiruzmakaban?",
"entries.mirror.label": "Maha melon fu {idx}/{total} shiruzmakaban ima...",
"entries.mirror.title": "Maha melon fu shiruzmakaban ima",
"entries.mirror.window_title": "Maha melon fu shiruzmakaban",
"entries.remove.plural.confirm": "Du kestetsa afto <b>{count}</b> shiruzmakaban we? Nil mlafu na shiruzmabaksu bli kestejena.",
"entries.remove.singular.confirm": "Du kestetsa afto shiruzmakaban long mlafuhuomi we? Nil mlafu na shiruzmabaksu bli kestejena.",
"entries.remove.plural.confirm": "Du kestetsa afto {count} shiruzmakaban?",
"entries.running.dialog.new_entries": "Nasii {total} neo shiruzmakaban fu mlafu ima...",
"entries.running.dialog.title": "Nasii neo shiruzmakaban fu mlafu ima",
"entries.tags": "Festaretol",
"entries.unlinked.description": "Tont shiruzmakaban fu mlafuhuomi tsunagajena na mlafu ine joku mlafukaban fu du. Li mlafu tsunagajena na shiruzmakaban ugokijena os kestejena ekso TagStudio, sit sore kntsunagajena.<br><br>Tsunaganaijena shiruzmakaban deki tsunaga gen na suha per mlafukaban fu du os keste li du vil.",
"entries.unlinked.description": "Tont shiruzmakaban fu mlafuhuomi tsunagajena na mlafu ine joku mlafukaban fu du. Li mlafu tsunagajena na shiruzmakaban ugokijena os kestejena ekso TagStudio, sit sore tsunaganaijena.<br><br>Tsunaganaijena shiruzmakaban deki tsunaga gen na suha per mlafukaban fu du os keste li du vil.",
"entries.unlinked.relink.attempting": "Iskat ima na tsunaga gen {index}/{unlinked_count} shiruzmakaban, {fixed_count} tsunagajena gen",
"entries.unlinked.relink.manual": "&Tsunaga gen mit hant",
"entries.unlinked.relink.title": "Tsunaga shiruzmakaban gen",
"entries.unlinked.remove": "Keste kntsunagajena shiruzmakaban",
"entries.unlinked.remove_alt": "Keste kntsunagajena shiruzmakaban (&v)",
"entries.unlinked.scanning": "Taskame mlafuhuomi ima grun kntsunagajena shiruzmakaban...",
"entries.unlinked.scanning": "Suha mlafuhuomi ima per tsunaganaijena shiruzmakaban...",
"entries.unlinked.search_and_relink": "&Suha &&Tsunaga gen",
"entries.unlinked.title": "Fiks kntsunagajena shiruzmakaban",
"entries.unlinked.unlinked_count": "Kntsunagajena shiruzmakaban: {count}",
"entries.unlinked.title": "Fiks tsunaganaijena shiruzmakaban",
"entries.unlinked.unlinked_count": "Tsunaganaijena shiruzmakaban: {count}",
"ffmpeg.missing.description": "FFmpeg au/os FFprobe nai finnajena. TagStudio treng FFmpeg per mahase riso.",
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
"field.copy": "Mverm shiruzmafal",
"field.edit": "Kawari shiruzmafal",
"field.paste": "Nasii shiruzmafal",
"field_type.datetime": "Dag-tid",
"field_type.text": "Tekst",
"field_type.unknown": "Fal nai shirujena",
"file.date_added": "Dag antajena",
"file.date_created": "Dag mahajena",
"file.date_modified": "Dag kawarijena",
"file.date_added": "Dag nasiijenadan",
"file.date_created": "Dag mahajenadan",
"file.date_modified": "Dag kawarijenadan",
"file.dimensions": "Stuuratailasku",
"file.duplicates.description": "TagStudio kjoka nasii shiruzma DupeGuru kara per jewalt blisnets mlafu.",
"file.duplicates.dupeguru.advice": "Za melon mahajena, du deki bruk DupeGuru per keste nai vilena mlafu. Sit, bruk tel fu TagStudio, haisa \"Fiks kntsunagajena shiruzmakaban\" na tropos sentakutumam per keste kntsunagajena shiruzmakaban.",
"file.duplicates.description": "TagStudio kjoka nasii shiruzma DupeGuru kara per jewalt mverm.",
"file.duplicates.dupeguru.advice": "Za melon mahajena, du deki bruk DupeGuru per keste nai vilena mlafu. Sit, bruk tel fu TagStudio, haisa \"Fiks tsunaganaijena shiruzmakaban\" na tropos sentakutumam per keste tsunaganaijena shiruzmakaban.",
"file.duplicates.dupeguru.file_extension": "DupeGuru mlafu (*.dupeguru)",
"file.duplicates.dupeguru.load_file": "&Gotova mlafu fu DupeGuru",
"file.duplicates.dupeguru.no_file": "Jamnai sentakujena mlafu fu DupeGuru",
"file.duplicates.dupeguru.open_file": "Auki sitan mlafu fu Dupeguru",
"file.duplicates.fix": "Fiks blisnets mlafu",
"file.duplicates.matches": "Blisnets-atai: {count}",
"file.duplicates.matches_uninitialized": "blisntes-atai: N/A",
"file.duplicates.mirror.description": "Maha melon fu shiruzma shiruzmakaban kara na al blisnets fu samakaban, afto zol visk al shiruzma, men zolnai keste os mverm shiruzmafal. Afto zolnai keste joku mlafu os shiruzma.",
"file.duplicates.dupeguru.open_file": "Auki shiruzma mlafu fu Dupeguru",
"file.duplicates.fix": "Fiks mverm",
"file.duplicates.matches": "Mverm-atai: {count}",
"file.duplicates.matches_uninitialized": "Mverm-atai: N/A",
"file.duplicates.mirror.description": "Maha melon fu shiruzma shiruzmakaban kara na al mverm fu samakaban, afto zol visk al shiruzma, men zolnai keste os mverm shiruzmafal. Afto zolnai keste joku mlafu os shiruzma.",
"file.duplicates.mirror_entries": "&Maha melon fu shiruzmakaban",
"file.duration": "Pitkatai",
"file.not_found": "Mlafu nai finnajena",
@@ -124,43 +110,33 @@
"generic.missing": "Harnai",
"generic.navigation.back": "Suruk",
"generic.navigation.next": "Mirai",
"generic.no": "Nai",
"generic.none": "Nil",
"generic.overwrite": "Vasu",
"generic.overwrite_alt": "&Vasu",
"generic.paste": "Nasii",
"generic.recent_libraries": "Moloda mlafuhuomi",
"generic.remove": "Keste",
"generic.remove_alt": "&Keste",
"generic.rename": "Haisagen",
"generic.rename_alt": "&Haisagen",
"generic.reset": "Gensintua",
"generic.save": "Ufne",
"generic.skip": "Pal",
"generic.skip_alt": "&Pal",
"generic.yes": "Akk",
"home.search": "Suha",
"home.search_entries": "Suha shiruzmakaban",
"home.search_library": "Suha mlafuhuomi",
"home.search_tags": "Suha festaretol",
"home.show_hidden_entries": "Mahase furijena shiruzmakaban",
"home.thumbnail_size": "Stuuratai fu risonen",
"home.thumbnail_size.extra_large": "Plusstuur risonen",
"home.thumbnail_size.large": "Stuur risonen",
"home.thumbnail_size.medium": "Mellan risonen",
"home.thumbnail_size.mini": "Chisai risonen",
"home.thumbnail_size.small": "Chisaidai risonen",
"ignore.open_file": "Mahase \"{ts_ignore}\" mlafu na shiruzmabaksu",
"json_migration.checking_for_parity": "Taskama per chigauzma ima...",
"json_migration.creating_database_tables": "Maha tumam na SQL Database ima...",
"json_migration.description": "<br>Hadji au de se shiruzma fu",
"json_migration.discrepancies_found": "Chiguazma finnajena na mlafuhuomi",
"json_migration.discrepancies_found.description": "Chiguazma finnajena mellan ranja au kjannos. Bitte gense au sentaku per beng na dlabdel os yamete.",
"json_migration.finish_migration": "Owari dlabdel",
"json_migration.heading.aliases": "Andrnamae:",
"json_migration.heading.colors": "Varge:",
"json_migration.heading.differ": "Tchigauzma",
"json_migration.heading.extensions": "Andr ilo:",
"json_migration.heading.match": "Finnajena sama",
"json_migration.heading.names": "Namae:",
"json_migration.heading.parent_tags": "Atama festaretol:",
@@ -180,22 +156,13 @@
"library.field.remove": "Keste shiruzmafal",
"library.missing": "Mlafuplas fu mlafuhuomi nai finnajenadan",
"library.name": "Mlafuhuomi",
"library.refresh.scanning.plural": "Taskame mlafukaban fu neo mlafu ima...\n{searched_count} mlafu suhajenadan, {found_count} neo mlafu finnajenadan",
"library.refresh.scanning.singular": "Taskame mlafukaban fu neo mlafu ima...\n{searched_count} mlafu suhajenadan, {found_count} neo mlafu finnajenadan",
"library.refresh.scanning_preparing": "Taskame mlafukaban fu neo mlafu ima...\nGotova ima...",
"library.refresh.scanning.plural": "Taskama mlafukaban fu neo mlafu ima...\n{searched_count} mlafu suhajenadan, {found_count} neo mlafu finnajenadan",
"library.refresh.scanning.singular": "Taskama mlafukaban fu neo mlafu ima...\n{searched_count} mlafu suhajenadan, {found_count} neo mlafu finnajenadan",
"library.refresh.scanning_preparing": "Taskama mlafukaban fu neo mlafu ima...\nGotova ima...",
"library.refresh.title": "Gengotova al mlafukaban",
"library.scan_library.title": "Taskame mlafuhuomi ima",
"library_info.cleanup": "Parjat",
"library_info.cleanup.backups": "Mverm long mlafuhuomi:",
"library_info.cleanup.dupe_files": "Blisnets mlafu:",
"library_info.cleanup.ignored": "Hestujena shiruzmakaban:",
"library_info.cleanup.legacy_json": "Gammel verso fu mlafuhuomi:",
"library_info.cleanup.unlinked": "Kntsunagajena shiruzmakaban:",
"library_info.stats.colors": "Varge fu festaretol:",
"library.scan_library.title": "Taskama mlafuhuomi ima",
"library_info.stats.entries": "Shiruzmakaban:",
"library_info.stats.fields": "Shiruzmafal:",
"library_info.stats.macros": "Aplinen:",
"library_info.stats.namespaces": "Tumam:",
"library_info.stats.tags": "Festaretol:",
"library_object.name": "Namae",
"library_object.name_required": "Namae (Trengjena)",
@@ -223,16 +190,15 @@
"menu.file.save_library": "Ufne mlafuhuomi",
"menu.help": "&Aputsa",
"menu.help.about": "Shiruplus",
"menu.macros": "&Aplinen",
"menu.macros": "&Zetingnen",
"menu.macros.folders_to_tags": "Mlafukaban festaretol made",
"menu.select": "Sentaku",
"menu.settings": "Sentakuzma...",
"menu.tools": "&Tropos",
"menu.tools.fix_duplicate_files": "Fiks &blisnets mlafu",
"menu.tools.fix_duplicate_files": "Fiks sama &mlafu",
"menu.tools.fix_unlinked_entries": "Fiks &tsunagajenanai shiruzmakaban",
"menu.view": "&Anse",
"menu.window": "Ekrannen",
"namespace.create.description_color": "Varge fu festaretol bruk tumam na vargekaban. Al neo varge mus long tumamklaani na eins.",
"namespace.create.title": "Maha vargetumam",
"namespace.new.button": "Neo vargetumam",
"namespace.new.prompt": "Maha neo vargetumam per nasii varge fu sebja!",
@@ -256,17 +222,17 @@
"settings.show_filenames_in_grid": "Mahase namae fu mlafu ine karta",
"settings.show_recent_libraries": "Mahase moloda mlafuhuomi",
"settings.theme.dark": "Kirai",
"settings.theme.label": "Ekran klea:",
"settings.theme.label": "Klea fu zeting:",
"settings.theme.light": "Kirkas",
"settings.theme.system": "Kompyu",
"settings.title": "Sentakuzma",
"sorting.direction.ascending": "Plus made",
"sorting.direction.descending": "Minus made",
"sorting.direction.ascending": "Leste owaris",
"sorting.direction.descending": "Leste eins",
"splash.opening_library": "Auki mlafuhuomi \"{library_path}\" ima...",
"status.deleted_file_plural": "Kestejenadan {count} mlafu!",
"status.deleted_file_singular": "Kestejenadan 1 mlafu!",
"status.deleted_none": "Nil mlafu kestejenadan.",
"status.deleted_partial_warning": "Mono kestejenadan {count} mlafu! Bitte taskamatsa li du dekinai finna mlafu os ke ima brukjena na andr apli.",
"status.deleted_partial_warning": "Mono kestejenadan {count} mlafu! Bitte taskamatsa li du dekinai finna mlafu os ke ima brukjena na andr zeting.",
"status.deleting_file": "Keste mlafu [{i}/{count}]: \"{path}\" ima...",
"status.library_closing": "Kini mlafuhuomi ima...",
"status.library_save_success": "Mlafuhuomi ufnejenadan au kinijenadan!",
@@ -284,7 +250,7 @@
"tag.all_tags": "Al festaretol",
"tag.choose_color": "Sentaku varge fu festaretol",
"tag.color": "Varge",
"tag.confirm_delete": "Du kestetsa festaretol \"{tag_name}\" we?",
"tag.confirm_delete": "Du kestetsa festaretol \"{tag_name}\"?",
"tag.create": "Maha festaretol",
"tag.create_add": "Maha && Nasii \"{query}\"",
"tag.disambiguation.tooltip": "Bruk afto festaretol grun plusklar",
@@ -306,8 +272,8 @@
"trash.context.singular": "Ugoki mlafu {trash_term} made",
"trash.dialog.disambiguation_warning.plural": "Afto zol keste tuo TagStudio kara <i>AU</i> kompyu fu du kara!",
"trash.dialog.disambiguation_warning.singular": "Afto zol keste sore TagStudio kara <i>AU</i> kompyu fu du kara!",
"trash.dialog.move.confirmation.plural": "Du ugokitsa afto {count} mlafu {trash_term} made we?",
"trash.dialog.move.confirmation.singular": "Du ugokitsa afto mlafu {trash_term} made we?",
"trash.dialog.move.confirmation.plural": "Du ugokitsa afto {count} mlafu {trash_term} made?",
"trash.dialog.move.confirmation.singular": "Du ugokitsa afto mlafu {trash_term} made?",
"trash.dialog.permanent_delete_warning": "<b>VIKTI!</b> Li afto mlafu nai dekijena ugoki {trash_term} made, sore zol <b>kestejena!!</b>",
"trash.dialog.title.plural": "Keste mlafu",
"trash.dialog.title.singular": "Keste mlafu",
Binary file not shown.
+1 -1
View File
@@ -29,7 +29,7 @@ def test_tag_widget_actions_replaced_correctly(qtbot: QtBot, qt_driver: QtDriver
# Set the widget
tags = library.tags
panel.set_tag_widget(tags[0], 0)
tag_widget: TagWidget = panel.scroll_layout.itemAt(0).widget() # pyright: ignore[reportAssignmentType]
tag_widget: TagWidget = panel.scroll_layout.itemAt(0).widget()
should_replace_actions = {
tag_widget: ["on_edit()", "on_remove()"],
-1
View File
@@ -30,7 +30,6 @@ EMPTY_LIBRARIES = "empty_libraries"
# str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_102")),
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_103")),
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_200")),
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_201")),
],
)
def test_library_migrations(path: str):