Compare commits

..

1 Commits

Author SHA1 Message Date
Jann Stute
cc275ec924 doc(install): add common error message help text 2026-05-13 21:38:51 +02:00
25 changed files with 131 additions and 94 deletions

View File

@@ -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

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

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

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:

View File

@@ -242,3 +242,20 @@ To generate thumbnails for RAR-based files (like `.cbr`) you'll need an extracto
### ripgrep
A recommended tool to improve the performance of directory scanning is [`ripgrep`](https://github.com/BurntSushi/ripgrep), a Rust-based directory walker that natively integrates with our [`.ts_ignore`](ignore.md) (`.gitignore`-style) pattern matching system for excluding files and directories. Ripgrep is already pre-installed on some Linux distributions and also available from several package managers.
## Common Error Messages
### Could not load the Qt platform plugin "xcb"
If you get an error message like this one:
```
qt.qpa.plugin: From 6.5.0, xcb-cursor0 or libxcb-cursor0 is needed to load the Qt xcb platform plugin.
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "/tmp/_MEIayuTiW/cv2/qt/plugins" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
Available platform plugins are: vnc, wayland-egl, offscreen, wayland, linuxfb, minimalegl, eglfs, minimal, vkkhrdisplay, xcb.
Aborted (core dumped)
```
Make sure you installed `libxcb-cursor` or `xcb-util-cursor`.

View File

@@ -42,9 +42,9 @@ 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 = [
@@ -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,7 +99,6 @@ 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
@@ -82,11 +106,11 @@ reportImportCycles = false
reportMissingTypeArgument = false
reportMissingTypeStubs = false
# reportOptionalMemberAccess = false
reportUnannotatedClassAttribute = false
reportUnknownArgumentType = false
reportUnknownLambdaType = false
reportUnknownMemberType = false
reportUnusedCallResult = false
reportUnannotatedClassAttribute = false
reportUninitializedInstanceVariable = false
[tool.ruff]

View File

@@ -32,7 +32,7 @@ class BaseField(Base):
@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:

View File

@@ -957,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])
@@ -1414,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()

View File

@@ -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]:

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

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()

View File

@@ -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)

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

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()

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)

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,

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)

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)

View File

@@ -299,7 +299,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 # pyright: ignore[reportArgumentType]
color="black",
)
draw = ImageDraw.Draw(im)
@@ -330,7 +330,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 # pyright: ignore[reportArgumentType]
color="#00000000",
)
draw = ImageDraw.Draw(im_hl)
@@ -349,7 +349,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 # pyright: ignore[reportArgumentType]
color="#00000000",
)
draw = ImageDraw.Draw(im_sh)
@@ -394,7 +394,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 # pyright: ignore[reportArgumentType]
color="#FF000000",
)
@@ -402,13 +402,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 # pyright: ignore[reportArgumentType]
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 # pyright: ignore[reportArgumentType]
bg_im = ImageEnhance.Brightness(bg_im).enhance(0.3) # Reduce the brightness
bg.paste(bg_im)
@@ -417,7 +417,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 # pyright: ignore[reportArgumentType]
(pixel_ratio * smooth_factor),
),
)
@@ -501,19 +501,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 # pyright: ignore[reportArgumentType]
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 # pyright: ignore[reportArgumentType]
# 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 # pyright: ignore[reportArgumentType]
color="#000000",
)
# Apply color overlay
@@ -527,7 +527,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 # pyright: ignore[reportArgumentType]
(pixel_ratio * smooth_factor),
),
)
@@ -1216,7 +1216,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(device=buffer, format="PNG") # type: ignore # pyright: ignore[reportArgumentType]
# Load the image from the buffer
im = Image.new("RGB", (size, size), color="#1e1e1e")
@@ -1338,7 +1338,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()

View File

@@ -2,6 +2,7 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
# Vendored from pydub
# type: ignore
import array

View File

@@ -350,7 +350,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")
@@ -1100,7 +1100,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)

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)