mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-05-21 08:15:09 +00:00
Compare commits
1 Commits
mypy-begon
...
doc/add-fa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc275ec924 |
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
# Vendored from pydub
|
||||
# type: ignore
|
||||
|
||||
|
||||
import array
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user