build: bump python version to 3.14 (#1485)

* build: bump python to 3.14

* ci: bump python to 3.14, update docs

* build: remove version condition for audioop-lts

* chore: formatting pass

* fix: disallow 3.15.0

* build: bump pyside version to 6.11.2

* fix(ui): allow valid None value for cancelButtonText to hide button
This commit is contained in:
Travis Abendshien
2026-08-19 14:22:25 -07:00
committed by GitHub
parent 112b27f059
commit f5a30489d7
63 changed files with 162 additions and 146 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ runs:
if: inputs.skip-setup != 'true'
uses: actions/setup-python@v6
with:
python-version: '3.12'
python-version: '3.14'
- name: Setup uv install
if: inputs.skip-setup != 'true'
+3 -3
View File
@@ -16,7 +16,7 @@ If you wish to develop for TagStudio, you'll need to create a development enviro
## Installing Python
Python [3.12](https://www.python.org/downloads) is required to develop for TagStudio. Any version matching "Python 3.12.x" should work, with "x" being any number. Alternatively you can use a tool such as [pyenv](https://github.com/pyenv/pyenv) to install this version of Python without affecting any existing Python installations on your system. Tools such as [uv](#installing-with-uv) can also install Python versions.
Python [3.14](https://www.python.org/downloads) is required to develop for TagStudio. Any version matching "Python 3.14.x" should work, with "x" being any number. Alternatively you can use a tool such as [pyenv](https://github.com/pyenv/pyenv) to install this version of Python without affecting any existing Python installations on your system. Tools such as [uv](#installing-with-uv) can also install Python versions.
<!-- prettier-ignore -->
!!! info "Python Aliases"
@@ -35,8 +35,8 @@ python --version
If you choose to install Python using pyenv, please refer to the following instructions:
1. Follow pyenv's [install instructions](https://github.com/pyenv/pyenv/?tab=readme-ov-file#installation) for your system.
2. Install the appropriate Python version with pyenv by running `pyenv install 3.12` (This will **not** mess with your existing Python installation).
3. Navigate to the repository root folder in your terminal and run `pyenv local 3.12`. You could alternatively use `pyenv shell 3.12` or `pyenv global 3.12` instead to set the Python version for the current terminal session or the entire system respectively, however using `local` is recommended.
2. Install the appropriate Python version with pyenv by running `pyenv install 3.14` (This will **not** mess with your existing Python installation).
3. Navigate to the repository root folder in your terminal and run `pyenv local 3.14`. You could alternatively use `pyenv shell 3.14` or `pyenv global 3.14` instead to set the Python version for the current terminal session or the entire system respectively, however using `local` is recommended.
---
+1 -1
View File
@@ -36,7 +36,7 @@ TagStudio has builds for :fontawesome-brands-windows: **Windows**, :fontawesome-
### :fontawesome-brands-python: Installing with PIP
TagStudio is installable via [PIP](https://pip.pypa.io/). Note that since we don't currently distribute on PyPI, the repository needs to be cloned and installed locally. Make sure you have Python 3.12 and PIP installed if you choose to install using this method.
TagStudio is installable via [PIP](https://pip.pypa.io/). Note that since we don't currently distribute on PyPI, the repository needs to be cloned and installed locally. Make sure you have Python 3.14 and PIP installed if you choose to install using this method.
The repository can be cloned/downloaded via `git` in your terminal, or by downloading the zip file from the "Code" button on the [repository page](https://github.com/TagStudioDev/TagStudio).
+4 -4
View File
@@ -12,9 +12,9 @@ description = "A User-Focused Photo & File Management System."
version = "9.6.4"
license = "GPL-3.0-only"
readme = "README.md"
requires-python = ">=3.12,<3.14"
requires-python = ">=3.14,<3.15"
dependencies = [
"audioop-lts; python_version >= '3.13'",
"audioop-lts~=0.2.2",
"chardet~=5.2",
"ffmpeg-python~=0.2",
"humanfriendly==10.*",
@@ -22,12 +22,12 @@ dependencies = [
"numpy~=2.2",
"opencv_python~=4.11",
"Pillow>=10.2,<12",
"pillow-heif~=0.22",
"pillow-heif~=1.5.0",
"pillow-jxl-plugin~=1.3",
"py7zr~=1.1.3",
"pydantic~=2.10",
"pydub~=0.25",
"PySide6==6.8.0.*",
"PySide6==6.11.2",
"rarfile==4.2",
"rawpy~=0.27",
"Send2Trash>=1.8,<3",
+13 -15
View File
@@ -57,7 +57,7 @@ class TagColorEnum(enum.IntEnum):
OLIVE = 37
@staticmethod
def get_color_from_str(color_name: str) -> "TagColorEnum":
def get_color_from_str(color_name: str) -> TagColorEnum:
for color in TagColorEnum:
if color.name == color_name.upper().replace(" ", "_"):
return color
@@ -99,17 +99,15 @@ class BrowsingState:
return Parser(self.query).parse()
@classmethod
def show_all(cls) -> "BrowsingState":
def show_all(cls) -> BrowsingState:
return BrowsingState()
@classmethod
def from_search_query(cls, search_query: str) -> "BrowsingState":
def from_search_query(cls, search_query: str) -> BrowsingState:
return cls(query=search_query)
@classmethod
def from_tag_id(
cls, tag_id: int | str, state: "BrowsingState | None" = None
) -> "BrowsingState":
def from_tag_id(cls, tag_id: int | str, state: BrowsingState | None = None) -> BrowsingState:
"""Create and return a BrowsingState object given a tag ID.
Args:
@@ -124,35 +122,35 @@ class BrowsingState:
return cls(query=f"tag_id:{str(tag_id)}")
@classmethod
def from_path(cls, path: Path | str) -> "BrowsingState":
def from_path(cls, path: Path | str) -> BrowsingState:
return cls(query=f'path:"{str(path).strip()}"')
@classmethod
def from_mediatype(cls, mediatype: str) -> "BrowsingState":
def from_mediatype(cls, mediatype: str) -> BrowsingState:
return cls(query=f"mediatype:{mediatype}")
@classmethod
def from_filetype(cls, filetype: str) -> "BrowsingState":
def from_filetype(cls, filetype: str) -> BrowsingState:
return cls(query=f"filetype:{filetype}")
@classmethod
def from_tag_name(cls, tag_name: str) -> "BrowsingState":
def from_tag_name(cls, tag_name: str) -> BrowsingState:
return cls(query=f'tag:"{tag_name}"')
def with_page_index(self, index: int) -> "BrowsingState":
def with_page_index(self, index: int) -> BrowsingState:
return replace(self, page_index=index)
def with_sorting_mode(self, mode: SortingModeEnum) -> "BrowsingState":
def with_sorting_mode(self, mode: SortingModeEnum) -> BrowsingState:
seed = self.random_seed
if mode == SortingModeEnum.RANDOM:
seed = random.random()
return replace(self, sorting_mode=mode, random_seed=seed)
def with_sorting_direction(self, ascending: bool) -> "BrowsingState":
def with_sorting_direction(self, ascending: bool) -> BrowsingState:
return replace(self, ascending=ascending)
def with_search_query(self, search_query: str) -> "BrowsingState":
def with_search_query(self, search_query: str) -> BrowsingState:
return replace(self, query=search_query)
def with_show_hidden_entries(self, show_hidden_entries: bool) -> "BrowsingState":
def with_show_hidden_entries(self, show_hidden_entries: bool) -> BrowsingState:
return replace(self, show_hidden_entries=show_hidden_entries)
+9 -9
View File
@@ -41,7 +41,7 @@ class TagAlias(Base):
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(nullable=False)
tag_id: Mapped[int] = mapped_column(ForeignKey("tags.id"))
tag: Mapped["Tag"] = relationship(back_populates="aliases")
tag: Mapped[Tag] = relationship(back_populates="aliases")
def __init__(self, name: str, tag_id: int | None = None):
self.name = name
@@ -97,14 +97,14 @@ class Tag(Base):
is_hidden: Mapped[bool]
icon: Mapped[str | None]
aliases: Mapped[set[TagAlias]] = relationship(back_populates="tag")
parent_tags: Mapped[set["Tag"]] = relationship(
parent_tags: Mapped[set[Tag]] = relationship(
secondary=TagParent.__tablename__,
primaryjoin="Tag.id == TagParent.child_id",
secondaryjoin="Tag.id == TagParent.parent_id",
back_populates="parent_tags",
)
disambiguation_id: Mapped[int | None]
category_exclusions: Mapped[set["Tag"]] = relationship(
category_exclusions: Mapped[set[Tag]] = relationship(
secondary=CategoryExclusion.__tablename__,
primaryjoin="Tag.id == CategoryExclusion.tag_id",
secondaryjoin="Tag.id == CategoryExclusion.category_id",
@@ -140,14 +140,14 @@ class Tag(Base):
id: int | None = None,
shorthand: str | None = None,
aliases: set[TagAlias] | None = None,
parent_tags: set["Tag"] | None = None,
parent_tags: set[Tag] | None = None,
icon: str | None = None,
color_namespace: str | None = None,
color_slug: str | None = None,
disambiguation_id: int | None = None,
is_category: bool = False,
is_hidden: bool = False,
category_exclusions: set["Tag"] | None = None,
category_exclusions: set[Tag] | None = None,
):
self.name = name
self.aliases = aliases or set()
@@ -181,16 +181,16 @@ class Tag(Base):
return False
return self.id == value.id
def __lt__(self, other: "Tag") -> bool:
def __lt__(self, other: Tag) -> bool:
return self.name < other.name
def __le__(self, other: "Tag") -> bool:
def __le__(self, other: Tag) -> bool:
return self.name <= other.name
def __gt__(self, other: "Tag") -> bool:
def __gt__(self, other: Tag) -> bool:
return self.name > other.name
def __ge__(self, other: "Tag") -> bool:
def __ge__(self, other: Tag) -> bool:
return self.name >= other.name
+1 -1
View File
@@ -861,7 +861,7 @@ class Library:
self.files_not_in_library,
key=lambda t: -(self.library_dir / t).stat().st_ctime,
)
except (FileExistsError, FileNotFoundError):
except FileExistsError, FileNotFoundError:
print(
"[LIBRARY] [ERROR] Couldn't sort files, some were moved during the scanning/sorting process."
)
+4 -4
View File
@@ -16,7 +16,7 @@ class ConstraintType(Enum):
Special = 5
@staticmethod
def from_string(text: str) -> "ConstraintType | None":
def from_string(text: str) -> ConstraintType | None:
return {
"tag": ConstraintType.Tag,
"tag_id": ConstraintType.TagID,
@@ -28,7 +28,7 @@ class ConstraintType(Enum):
class AST:
parent: "AST | None" = None
parent: AST | None = None
@override
def __str__(self):
@@ -65,9 +65,9 @@ class ORList(AST):
class Constraint(AST):
type: ConstraintType
value: str
properties: list["Property"]
properties: list[Property]
def __init__(self, type: ConstraintType, value: str, properties: list["Property"]) -> None:
def __init__(self, type: ConstraintType, value: str, properties: list[Property]) -> None:
super().__init__()
for prop in properties:
prop.parent = self
+2 -2
View File
@@ -39,11 +39,11 @@ class Token:
self.end = end
@staticmethod
def from_type(type: TokenType, pos: int) -> "Token":
def from_type(type: TokenType, pos: int) -> Token:
return Token(type, None, pos, pos)
@staticmethod
def EOF(pos: int) -> "Token": # noqa: N802
def EOF(pos: int) -> Token: # noqa: N802
return Token.from_type(TokenType.EOF, pos)
@override
+1 -1
View File
@@ -58,5 +58,5 @@ def format_duration(duration: int | float) -> str:
hours, seconds = divmod(seconds, 3600)
minutes, seconds = divmod(seconds, 60)
return f"{hours}:{minutes:02}:{seconds:02}" if hours else f"{minutes}:{seconds:02}"
except (OverflowError, ValueError):
except OverflowError, ValueError:
return "-:--"
+1 -1
View File
@@ -86,7 +86,7 @@ class Translator:
def __format(self, text: str, **kwargs: ...) -> str:
try:
return text.format(**kwargs)
except (KeyError, ValueError):
except KeyError, ValueError:
logger.error(
"[Translations] Error while formatting translation.",
text=text,
+1 -1
View File
@@ -52,7 +52,7 @@ class TarFile:
def read(self, name: str) -> bytes:
return unwrap(self.tar.extractfile(name)).read()
def __enter__(self) -> "TarFile":
def __enter__(self) -> TarFile:
self.tar = tarfile.open(name=self.filepath, mode=self.mode).__enter__()
return self
+1 -1
View File
@@ -52,7 +52,7 @@ def audio_album_thumb(filepath: Path, ext: str) -> Image.Image | None:
image = artwork
except (
FileNotFoundError,
id3.ID3NoHeaderError, # pyright: ignore[reportPrivateImportUsage]
id3.ID3NoHeaderError,
mp4.MP4MetadataError,
mp4.MP4StreamInfoError,
MutagenError,
+1 -1
View File
@@ -88,7 +88,7 @@ class AppSettings(BaseModel):
loaded_from: Path = Field(default=DEFAULT_GLOBAL_SETTINGS_PATH, exclude=True)
@staticmethod
def read_settings(path: Path = DEFAULT_GLOBAL_SETTINGS_PATH) -> "AppSettings":
def read_settings(path: Path = DEFAULT_GLOBAL_SETTINGS_PATH) -> AppSettings:
if path.exists():
with open(path) as file:
filecontents = file.read()
@@ -77,4 +77,4 @@ class AutofillLineEdit(QLineEdit):
# Filter out icon action(s)
if action.text():
menu.addAction(action)
menu.exec(self.mapToGlobal(pos)) # pyright: ignore[reportArgumentType]
menu.exec(self.mapToGlobal(pos))
@@ -11,6 +11,7 @@ from PySide6.QtWidgets import QWidget
from tagstudio.core.library.alchemy.fields import BaseFieldTemplate
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.utils.types import unwrap
from tagstudio.i18n.translations import Translations
from tagstudio.qt.app_settings import AppSettings
from tagstudio.qt.controllers.edit_field_template_modal import EditFieldTemplateModal
@@ -155,6 +156,7 @@ class FieldSuggestBox(SuggestBox[BaseFieldTemplate]):
widget.setHidden(True)
self.layout().content_layout.addWidget(widget)
widget_: QWidget = self.layout().content_layout.itemAt(index).widget()
item = unwrap(self.layout().content_layout.itemAt(index))
widget_: QWidget = unwrap(item.widget())
assert isinstance(widget_, UnderlinedWidget)
return widget_
@@ -11,6 +11,7 @@ from PySide6.QtWidgets import QMessageBox, QWidget
from tagstudio.core.library.alchemy.fields import BaseFieldTemplate
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.utils.types import unwrap
from tagstudio.i18n.translations import Translations
from tagstudio.qt.controllers.edit_field_template_modal import EditFieldTemplateModal
from tagstudio.qt.controllers.field_template_widget import FieldTemplateWidget
@@ -173,6 +174,7 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]):
pad_field_template_widget.setHidden(True)
self.layout().scroll_layout.addWidget(pad_field_template_widget)
field_template_widget: QWidget = self.layout().scroll_layout.itemAt(index).widget()
item = unwrap(self.layout().scroll_layout.itemAt(index))
field_template_widget: QWidget = unwrap(item.widget())
assert isinstance(field_template_widget, FieldTemplateWidget)
return field_template_widget
@@ -24,7 +24,7 @@ logger = structlog.get_logger(__name__)
# TODO: Use newer MVC style guidelines
class FixIgnoredEntriesModal(FixIgnoredEntriesModalView):
def __init__(self, library: "Library", driver: "QtDriver"):
def __init__(self, library: Library, driver: QtDriver):
super().__init__(library, driver)
self.tracker = IgnoredRegistry(self.lib)
+1 -1
View File
@@ -45,7 +45,7 @@ class _ItemMode(IntEnum):
class Inspector(QWidget):
def __init__(self, driver: "QtDriver") -> None:
def __init__(self, driver: QtDriver) -> None:
super().__init__()
self._driver = driver
self._lib = self._driver.lib
@@ -33,7 +33,7 @@ logger = structlog.get_logger(__name__)
# TODO: Use newer MVC style guidelines
class LibraryInfoWindow(LibraryInfoWindowView):
def __init__(self, library: "Library", driver: "QtDriver"):
def __init__(self, library: Library, driver: QtDriver):
super().__init__(library, driver)
# Statistics Buttons
+5 -5
View File
@@ -458,7 +458,7 @@ class MainWindow(QMainWindow):
(Translations["home.thumbnail_size.mini"], 76),
]
def __init__(self, driver: "QtDriver", parent: QWidget | None = None) -> None:
def __init__(self, driver: QtDriver, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.rm = ResourceManager()
@@ -529,7 +529,7 @@ class MainWindow(QMainWindow):
# endregion
def setup_central_widget(self, driver: "QtDriver"):
def setup_central_widget(self, driver: QtDriver):
self.central_widget = QWidget(self)
self.central_widget.setObjectName("central_widget")
self.central_layout = QGridLayout(self.central_widget)
@@ -651,7 +651,7 @@ class MainWindow(QMainWindow):
self.central_layout.addLayout(self.extra_input_layout, 5, 0, 1, 1)
def setup_content(self, driver: "QtDriver"):
def setup_content(self, driver: QtDriver):
self.content_layout = QHBoxLayout()
self.content_layout.setObjectName("content_layout")
@@ -667,7 +667,7 @@ class MainWindow(QMainWindow):
self.central_layout.addLayout(self.content_layout, 10, 0, 1, 1)
def setup_entry_list(self, driver: "QtDriver"):
def setup_entry_list(self, driver: QtDriver):
self.entry_list_container = QWidget()
self.entry_list_layout = QVBoxLayout(self.entry_list_container)
self.entry_list_layout.setSpacing(0)
@@ -700,7 +700,7 @@ class MainWindow(QMainWindow):
self.entry_list_layout.addWidget(self.pagination)
self.content_splitter.addWidget(self.entry_list_container)
def setup_preview_panel(self, driver: "QtDriver"):
def setup_preview_panel(self, driver: QtDriver):
self.preview_panel = Inspector(driver)
self.content_splitter.addWidget(self.preview_panel)
@@ -18,7 +18,7 @@ if typing.TYPE_CHECKING:
class MergeDuplicateEntriesProgress(QObject):
done = Signal()
def __init__(self, library: "Library", driver: "QtDriver"):
def __init__(self, library: Library, driver: QtDriver):
super().__init__()
self.lib = library
self.driver = driver
@@ -33,7 +33,7 @@ Image.MAX_IMAGE_PIXELS = None
# TODO: Use newer MVC style guidelines
class PreviewThumb(PreviewThumbView):
def __init__(self, library: Library, driver: "QtDriver"):
def __init__(self, library: Library, driver: QtDriver):
super().__init__(library, driver)
self.__driver: QtDriver = driver
+4 -4
View File
@@ -27,10 +27,10 @@ class ProgressWidget(QWidget):
super().__init__()
self.root = QVBoxLayout(self)
self.pb = QProgressDialog(
labelText=label_text,
minimum=minimum,
cancelButtonText=cancel_button_text, # pyright: ignore[reportArgumentType]
maximum=maximum,
label_text,
cancel_button_text, # pyright: ignore[reportArgumentType]
minimum,
maximum,
)
self.root.addWidget(self.pb)
self.setFixedSize(432, 112)
+2 -2
View File
@@ -78,7 +78,7 @@ class SearchPanel[T](ModalContent):
self.setMinimumSize(300, 400)
self.connect_callbacks(self)
def connect_callbacks(self, controller: "SearchPanel[Any]") -> None: # pyright: ignore[reportExplicitAny]
def connect_callbacks(self, controller: SearchPanel[Any]) -> None: # pyright: ignore[reportExplicitAny]
self.layout().limit_combobox.currentIndexChanged.connect(controller.on_limit_changed)
self.layout().search_field.textChanged.connect(controller.on_search_query_changed)
self.layout().search_field.returnPressed.connect(
@@ -166,7 +166,7 @@ class SearchPanel[T](ModalContent):
if not query:
self.layout().search_field.setFocus()
parent: QWidget | None = self.parentWidget()
if parent is not None: # pyright: ignore[reportUnnecessaryComparison]
if parent is not None:
parent.hide()
return
+7 -4
View File
@@ -11,6 +11,7 @@ from PySide6.QtGui import QAction, QPixmap, QShowEvent, Qt
from PySide6.QtWidgets import QGraphicsOpacityEffect, QWidget
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.utils.types import unwrap
from tagstudio.i18n.translations import Translations
from tagstudio.qt.app_settings import AppSettings
from tagstudio.qt.controllers.autofill_line_edit import QtCore, QtGui
@@ -110,7 +111,7 @@ class SuggestBox[T](QWidget):
def _on_shift_held(self, held: bool) -> None:
self._is_shift_held = held
for i in range(0, self.layout().content_layout.count()):
underlined_widget = self.layout().content_layout.itemAt(i).widget()
underlined_widget = self.layout().content_layout.itemAt(i).widget() # pyright: ignore
assert isinstance(underlined_widget, UnderlinedWidget)
if held and i == self._selection_index:
@@ -128,7 +129,7 @@ class SuggestBox[T](QWidget):
# Initialize the widget count (non-hidden)
widget_count = 0
for i in range(0, self.layout().content_layout.count()):
widget = self.layout().content_layout.itemAt(i).widget()
widget = unwrap(self.layout().content_layout.itemAt(i).widget()) # pyright: ignore
if not widget.isHidden():
widget_count += 1
@@ -149,7 +150,7 @@ class SuggestBox[T](QWidget):
# Draw the correct underline for the selected widget
for i in range(0, widget_count):
underlined_widget = self.layout().content_layout.itemAt(i).widget()
underlined_widget = self.layout().content_layout.itemAt(i).widget() # pyright: ignore
assert isinstance(underlined_widget, UnderlinedWidget)
if i == self._selection_index:
underlined_widget.toggle_underline(is_hidden=False)
@@ -216,7 +217,9 @@ class SuggestBox[T](QWidget):
self._selection_index = 0
if self.layout().content_layout.count() > 0:
self.layout().scroll_area.ensureWidgetVisible(
self.layout().content_layout.itemAt(0).widget(), xmargin=16, ymargin=0
self.layout().content_layout.itemAt(0).widget(), # pyright: ignore
xmargin=16,
ymargin=0,
)
# Get results for the search query
+1 -1
View File
@@ -28,7 +28,7 @@ class TagBoxWidget(TagBoxWidgetView):
__entries: list[int] = []
def __init__(self, title: str, driver: "QtDriver"):
def __init__(self, title: str, driver: QtDriver):
super().__init__(title, driver)
self.__driver = driver
@@ -12,6 +12,7 @@ from PySide6.QtWidgets import QMessageBox, QWidget
from tagstudio.core.constants import RESERVED_TAG_END, RESERVED_TAG_START
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import Tag
from tagstudio.core.utils.types import unwrap
from tagstudio.i18n.translations import Translations
from tagstudio.qt.controllers.modal import Modal
from tagstudio.qt.controllers.modal_content import ModalContent
@@ -207,6 +208,7 @@ class TagSearchPanel(SearchPanel[Tag]):
pad_tag_widget.setHidden(True)
self.layout().scroll_layout.addWidget(pad_tag_widget)
tag_widget: QWidget = self.layout().scroll_layout.itemAt(index).widget()
item = unwrap(self.layout().scroll_layout.itemAt(index))
tag_widget: QWidget = unwrap(item.widget())
assert isinstance(tag_widget, TagWidget)
return tag_widget
@@ -12,6 +12,7 @@ from PySide6.QtWidgets import QGraphicsOpacityEffect, QWidget
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import Tag
from tagstudio.core.utils.types import unwrap
from tagstudio.i18n.translations import Translations
from tagstudio.qt.app_settings import AppSettings
from tagstudio.qt.controllers.modal import Modal
@@ -191,6 +192,7 @@ class TagSuggestBox(SuggestBox[Tag]):
widget.setHidden(True)
self.layout().content_layout.addWidget(widget)
widget_: QWidget = self.layout().content_layout.itemAt(index).widget()
item = unwrap(self.layout().content_layout.itemAt(index))
widget_: QWidget = unwrap(item.widget())
assert isinstance(widget_, UnderlinedWidget)
return widget_
@@ -6,6 +6,7 @@ from typing import override
from PySide6.QtWidgets import QWidget
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.views.underlined_widget_view import UnderlinedWidgetView
@@ -21,7 +22,7 @@ class UnderlinedWidget(QWidget):
@property
def widget(self) -> QWidget:
return self.layout().itemAt(0).widget()
return unwrap(unwrap(self.layout().itemAt(0)).widget())
@override
def layout(self) -> UnderlinedWidgetView:
+2 -2
View File
@@ -359,7 +359,7 @@ class BuildTagPanel(ModalContent):
def set_categories(self, added_parent_id: int | None = None, removed_parent: bool = False):
while self.category_scroll_layout.itemAt(0):
self.category_scroll_layout.takeAt(0).widget().deleteLater()
self.category_scroll_layout.takeAt(0).widget().deleteLater() # pyright: ignore[reportOptionalMemberAccess]
c = QWidget()
layout = QVBoxLayout(c)
@@ -480,7 +480,7 @@ class BuildTagPanel(ModalContent):
def set_parent_tags(self):
while self.parent_tags_scroll_layout.itemAt(0):
self.parent_tags_scroll_layout.takeAt(0).widget().deleteLater()
self.parent_tags_scroll_layout.takeAt(0).widget().deleteLater() # pyright: ignore[reportOptionalMemberAccess]
c = QWidget()
layout = QVBoxLayout(c)
+3 -3
View File
@@ -33,8 +33,8 @@ class ColorBoxWidget(FieldWidget):
def __init__(
self,
group: str,
colors: list["TagColorGroup"],
library: "Library",
colors: list[TagColorGroup],
library: Library,
) -> None:
self.namespace = group
self.colors: list[TagColorGroup] = colors
@@ -60,7 +60,7 @@ class ColorBoxWidget(FieldWidget):
color_widgets: list[TagColorLabel] = []
while self.base_layout.itemAt(0):
unwrap(self.base_layout.takeAt(0)).widget().deleteLater()
unwrap(self.base_layout.takeAt(0)).widget().deleteLater() # pyright: ignore[reportOptionalMemberAccess]
for color in colors_:
color_widget = TagColorLabel(
+1 -1
View File
@@ -41,7 +41,7 @@ def qdtf2dtf(dtf: str) -> str:
# TODO: Split to use MVC guidelines.
class DatetimePicker(ModalContent):
def __init__(self, driver: "QtDriver", name: str, datetime: dt | str):
def __init__(self, driver: QtDriver, name: str, datetime: dt | str):
super().__init__()
self.setMinimumSize(300, 60)
self.root_layout = QVBoxLayout(self)
+1 -1
View File
@@ -34,7 +34,7 @@ class DuplicateChoice(enum.StrEnum):
class DropImportModal(QWidget):
DUPE_NAME_LIMT: int = 5
def __init__(self, driver: "QtDriver"):
def __init__(self, driver: QtDriver):
super().__init__()
self.driver: QtDriver = driver
+2 -2
View File
@@ -50,7 +50,7 @@ class FieldContainers(QWidget):
on_tags_update = Signal()
def __init__(self, library: Library, driver: "QtDriver") -> None:
def __init__(self, library: Library, driver: QtDriver) -> None:
super().__init__()
self.lib = library
@@ -321,7 +321,7 @@ class FieldContainers(QWidget):
text = self.driver.settings.format_datetime(
DatetimePicker.string2dt(field.value)
)
except (ValueError, AssertionError):
except ValueError, AssertionError:
text = str(field.value)
else:
text = f"<i>{Translations['field.mixed_data']}</i>"
+8 -5
View File
@@ -12,6 +12,7 @@ from PySide6.QtCore import QEvent, QSize, Qt
from PySide6.QtGui import QEnterEvent, QPixmap, QResizeEvent
from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.resource_manager import ResourceManager
from tagstudio.qt.views.styles.color_overlay import auto_theme_overlay
from tagstudio.qt.views.styles.stylesheets import container_style, header
@@ -136,17 +137,19 @@ class FieldContainer(QWidget):
if callback:
self.remove_button.clicked.connect(callback)
def set_inner_widget(self, widget: "FieldWidget") -> None:
if self.field_layout.itemAt(0):
old: QWidget = self.field_layout.itemAt(0).widget()
def set_inner_widget(self, widget: FieldWidget) -> None:
item = self.field_layout.itemAt(0)
if item:
old: QWidget = unwrap(item.widget())
self.field_layout.removeWidget(old)
old.deleteLater()
self.field_layout.addWidget(widget)
def get_inner_widget(self) -> QWidget | None:
if self.field_layout.itemAt(0):
return self.field_layout.itemAt(0).widget()
item = self.field_layout.itemAt(0)
if item:
return item.widget()
return None
def set_title(self, title: str) -> None:
+4 -4
View File
@@ -42,7 +42,7 @@ class FileAttributeData:
# TODO: Split to use MVC guidelines.
class FileAttributes(QWidget):
def __init__(self, library: Library, driver: "QtDriver"):
def __init__(self, library: Library, driver: QtDriver):
super().__init__()
root_layout = QVBoxLayout(self)
root_layout.setContentsMargins(0, 0, 0, 0)
@@ -130,7 +130,7 @@ class FileAttributes(QWidget):
stats = FileAttributeData()
if not filepath:
self.layout().setSpacing(0)
self.layout().setSpacing(0) # pyright: ignore[reportOptionalMemberAccess]
self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.file_label.setText(f"<i>{Translations['preview.no_selection']}</i>")
self.file_label.set_file_path(Path())
@@ -147,7 +147,7 @@ class FileAttributes(QWidget):
elif self.driver.settings.show_filepath == ShowFilepathOption.SHOW_FILENAMES_ONLY:
display_path = Path(filepath.name)
self.layout().setSpacing(6)
self.layout().setSpacing(6) # pyright: ignore[reportOptionalMemberAccess]
self.file_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.file_label.set_file_path(filepath)
self.dimensions_label.setHidden(False)
@@ -234,7 +234,7 @@ class FileAttributes(QWidget):
def update_multi_selection(self, count: int):
"""Format attributes for multiple selected items."""
self.layout().setSpacing(0)
self.layout().setSpacing(0) # pyright: ignore[reportOptionalMemberAccess]
self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.file_label.setText(Translations.format("preview.multiple_selection", count=count))
self.file_label.setCursor(Qt.CursorShape.ArrowCursor)
+1 -1
View File
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
# TODO: Split to use MVC guidelines.
class FixDupeFilesModal(QWidget):
def __init__(self, library: "Library", driver: "QtDriver"):
def __init__(self, library: Library, driver: QtDriver):
super().__init__()
self.lib = library
self.driver = driver
+1 -1
View File
@@ -24,7 +24,7 @@ if TYPE_CHECKING:
# TODO: Split to use MVC guidelines.
class FixUnlinkedEntriesModal(QWidget):
def __init__(self, library: "Library", driver: "QtDriver"):
def __init__(self, library: Library, driver: QtDriver):
super().__init__()
self.lib = library
self.driver = driver
+4 -3
View File
@@ -1,11 +1,13 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
# pyright: reportOptionalMemberAccess=false
import math
from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, override
from warnings import deprecated
import structlog
from PySide6 import QtCore, QtGui
@@ -19,7 +21,6 @@ from PySide6.QtWidgets import (
QVBoxLayout,
QWidget,
)
from typing_extensions import deprecated
from tagstudio.core.constants import TAG_ARCHIVED, TAG_FAVORITE
from tagstudio.core.library.alchemy.enums import TagColorEnum
@@ -39,7 +40,7 @@ logger = structlog.get_logger(__name__)
@dataclass
class BranchData:
dirs: dict[str, "BranchData"] = field(default_factory=dict)
dirs: dict[str, BranchData] = field(default_factory=dict)
files: list[str] = field(default_factory=list)
tag: Tag | None = None
@@ -165,7 +166,7 @@ def generate_preview_data(library: Library) -> BranchData:
class FoldersToTagsModal(QWidget):
def __init__(self, library: "Library", driver: "QtDriver"):
def __init__(self, library: Library, driver: QtDriver):
super().__init__()
self.library = library
self.driver = driver
+2 -2
View File
@@ -96,7 +96,7 @@ class ItemThumb(FlowWidget):
self,
mode: ItemType | None,
library: Library,
driver: "QtDriver",
driver: QtDriver,
thumb_size: tuple[int, int],
show_filename_label: bool = False,
):
@@ -440,7 +440,7 @@ class ItemThumb(FlowWidget):
self.thumb_button.setMinimumSize(size)
self.thumb_button.setMaximumSize(size)
def set_item(self, entry: "Entry"):
def set_item(self, entry: Entry):
self.set_item_id(entry.id)
path = unwrap(self.lib.library_dir) / entry.path
self.set_item_path(path)
+1 -1
View File
@@ -29,7 +29,7 @@ class LandingWidget(QWidget):
mono_logo: Image.Image = rm.ts_logo_text_mono
color_logo: Image.Image = rm.ts_logo_text_color
def __init__(self, driver: "QtDriver", pixel_ratio: float):
def __init__(self, driver: QtDriver, pixel_ratio: float):
super().__init__()
self.driver = driver
self.logo_label: ClickableLabel = ClickableLabel()
+2 -2
View File
@@ -53,9 +53,9 @@ class MediaPlayer(QGraphicsView):
Gives a basic control set to manage media playback.
"""
video_preview: "VideoPreview | None" = None
video_preview: VideoPreview | None = None
def __init__(self, driver: "QtDriver") -> None:
def __init__(self, driver: QtDriver) -> None:
super().__init__()
self.driver = driver
self.play_icon = QPixmap.fromImage(
+17 -22
View File
@@ -5,6 +5,7 @@
import traceback
from pathlib import Path
from typing import cast
from warnings import deprecated
import structlog
import wcmatch.fnmatch as fnmatch
@@ -23,7 +24,6 @@ from PySide6.QtWidgets import (
)
from sqlalchemy import select
from sqlalchemy.orm import Session
from typing_extensions import deprecated
from tagstudio.core.constants import (
IGNORE_NAME,
@@ -98,8 +98,8 @@ class JsonMigrationModal(QObject):
body_label = QLabel(Translations["json_migration.info.description"])
body_label.setWordWrap(True)
body_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
body_wrapper.layout().addWidget(body_label)
body_wrapper.layout().setContentsMargins(0, 36, 0, 0)
body_wrapper.layout().addWidget(body_label) # pyright: ignore[reportOptionalMemberAccess]
body_wrapper.layout().setContentsMargins(0, 36, 0, 0) # pyright: ignore[reportOptionalMemberAccess]
cancel_button = QPushButton(Translations["generic.cancel"])
next_button = QPushButton(Translations["generic.continue"])
@@ -287,9 +287,9 @@ class JsonMigrationModal(QObject):
body_container_layout.addStretch(1)
body_container_layout.addWidget(new_lib_container)
body_container_layout.addStretch(2)
self.body_wrapper_01.layout().addWidget(body_container)
self.body_wrapper_01.layout().addWidget(desc_label)
self.body_wrapper_01.layout().setSpacing(12)
self.body_wrapper_01.layout().addWidget(body_container) # pyright: ignore[reportOptionalMemberAccess]
self.body_wrapper_01.layout().addWidget(desc_label) # pyright: ignore[reportOptionalMemberAccess]
self.body_wrapper_01.layout().setSpacing(12) # pyright: ignore[reportOptionalMemberAccess]
back_button = QPushButton(Translations["generic.navigation.back"])
start_button = QPushButton(Translations["json_migration.start_and_preview"])
@@ -352,14 +352,9 @@ class JsonMigrationModal(QObject):
def migration_progress(self, skip_ui: bool = False):
"""Initialize the progress bar and iterator for the library migration."""
pb = QProgressDialog(
labelText="",
cancelButtonText="",
minimum=0,
maximum=0,
)
pb.setCancelButton(None) # pyright: ignore[reportArgumentType]
self.body_wrapper_01.layout().addWidget(pb)
pb = QProgressDialog("", "", 0, 0)
pb.setCancelButton(None)
self.body_wrapper_01.layout().addWidget(pb) # pyright: ignore[reportOptionalMemberAccess]
try:
iterator = FunctionIterator(self.migration_iterator)
@@ -480,26 +475,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() # pyright: 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() # pyright: 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() # pyright: ignore
warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # pyright: 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() # pyright: ignore
new_label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore
old_warning_icon: QLabel = self.old_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore
new_warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # pyright: 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)
@@ -22,7 +22,7 @@ if typing.TYPE_CHECKING:
class MirrorEntriesModal(QWidget):
done = Signal()
def __init__(self, driver: "QtDriver", tracker: DupeFilesRegistry):
def __init__(self, driver: QtDriver, tracker: DupeFilesRegistry):
super().__init__()
self.driver = driver
self.setWindowTitle(Translations["entries.mirror.window_title"])
+3 -2
View File
@@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
# pyright: reportOptionalMemberAccess=false
from typing import override
import structlog
@@ -9,6 +9,7 @@ from PySide6 import QtCore, QtGui
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.mixed.paged_panel_state import PagedPanelState
from tagstudio.qt.views.styles.stylesheets import header
@@ -96,7 +97,7 @@ class PagedPanel(QWidget):
# Update Body Widget
if self.body_layout.itemAt(0):
self.body_layout.itemAt(0).widget().setHidden(True)
self.body_layout.removeWidget(self.body_layout.itemAt(0).widget())
self.body_layout.removeWidget(unwrap(self.body_layout.itemAt(0).widget()))
self.body_layout.addWidget(frame.body_wrapper)
self.body_layout.itemAt(0).widget().setHidden(False)
+1
View File
@@ -1,6 +1,7 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
# pyright: reportOptionalMemberAccess=false
from typing import cast, override
from warnings import catch_warnings
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
class RemoveIgnoredModal(QWidget):
done = Signal()
def __init__(self, driver: "QtDriver", tracker: IgnoredRegistry):
def __init__(self, driver: QtDriver, tracker: IgnoredRegistry):
super().__init__()
self.driver = driver
self.tracker = tracker
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
class RemoveUnlinkedEntriesModal(QWidget):
done = Signal()
def __init__(self, driver: "QtDriver", tracker: UnlinkedRegistry):
def __init__(self, driver: QtDriver, tracker: UnlinkedRegistry):
super().__init__()
self.driver = driver
self.tracker = tracker
+4 -4
View File
@@ -41,7 +41,7 @@ logger = structlog.get_logger(__name__)
# TODO: Split to use MVC guidelines.
class SettingsPanel(ModalContent):
driver: "QtDriver"
driver: QtDriver
filepath_option_map: dict[ShowFilepathOption, str] = {
ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"],
@@ -88,7 +88,7 @@ class SettingsPanel(ModalContent):
"%Y.%m.%d": "2024.08.21",
}
def __init__(self, driver: "QtDriver"):
def __init__(self, driver: QtDriver):
super().__init__()
# set these "constants" because language will be loaded from config shortly after startup
# and we want to use the current language for the dropdowns
@@ -400,7 +400,7 @@ class SettingsPanel(ModalContent):
"splash": self.splash_combobox.currentData(),
}
def update_settings(self, driver: "QtDriver"):
def update_settings(self, driver: QtDriver):
settings = self.get_settings()
driver.settings.language = settings["language"]
@@ -440,7 +440,7 @@ class SettingsPanel(ModalContent):
)
@classmethod
def build_modal(cls, driver: "QtDriver") -> Modal:
def build_modal(cls, driver: QtDriver) -> Modal:
settings_panel = cls(driver)
modal = Modal(
+1 -1
View File
@@ -41,7 +41,7 @@ class TagColorLabel(QWidget):
color: TagColorGroup | None,
has_edit: bool,
has_remove: bool,
library: "Library | None" = None,
library: Library | None = None,
) -> None:
super().__init__()
self.color = color
+4 -2
View File
@@ -23,6 +23,7 @@ from PySide6.QtWidgets import (
from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX
from tagstudio.core.enums import Theme
from tagstudio.core.utils.types import unwrap
from tagstudio.i18n.translations import Translations
from tagstudio.qt.controllers.modal import Modal
from tagstudio.qt.mixed.build_namespace import BuildNamespacePanel
@@ -42,7 +43,7 @@ class TagColorManager(QWidget):
def __init__(
self,
driver: "QtDriver",
driver: QtDriver,
):
super().__init__()
self.driver = driver
@@ -166,7 +167,8 @@ class TagColorManager(QWidget):
def reset(self):
while self.scroll_layout.count():
widget = self.scroll_layout.itemAt(0).widget()
item = unwrap(self.scroll_layout.itemAt(0))
widget = unwrap(item.widget())
self.scroll_layout.removeWidget(widget)
widget.deleteLater()
self.is_initialized = False
+1 -1
View File
@@ -32,7 +32,7 @@ class TagColorPreview(QWidget):
def __init__(
self,
library: "Library",
library: Library,
tag_color_group: TagColorGroup | None,
) -> None:
super().__init__()
+1 -1
View File
@@ -111,7 +111,7 @@ class TagWidget(QWidget):
tag: Tag | None
def __init__(
self, tag: Tag | None, has_edit: bool, has_remove: bool, library: "Library | None" = None
self, tag: Tag | None, has_edit: bool, has_remove: bool, library: Library | None = None
) -> None:
super().__init__()
self.tag = tag
+1 -1
View File
@@ -20,7 +20,7 @@ class ResourceManager:
_map: dict[str, dict[str, str]] = {}
_cache: dict[str, bytes | str | Image.Image | QPixmap] = {}
_instance: "ResourceManager | None" = None
_instance: ResourceManager | None = None
def __new__(cls):
if ResourceManager._instance is None:
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
# TODO: Use newer MVC style guidelines
class FixIgnoredEntriesModalView(QWidget):
def __init__(self, library: "Library", driver: "QtDriver"):
def __init__(self, library: Library, driver: QtDriver):
super().__init__()
self.lib = library
self.driver = driver
+1 -1
View File
@@ -28,7 +28,7 @@ logger = structlog.get_logger(__name__)
class InspectorView(QVBoxLayout):
def __init__(self, driver: "QtDriver", pixel_ratio: float) -> None:
def __init__(self, driver: QtDriver, pixel_ratio: float) -> None:
super().__init__()
self.setContentsMargins(0, 0, 0, 0)
self.setSpacing(6)
@@ -3,6 +3,8 @@
# SPDX-FileCopyrightText: (C) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
# pyright: reportOptionalMemberAccess=false
"""PySide6 port of the widgets/layouts/flowlayout example from Qt v6.x."""
@@ -44,14 +46,14 @@ class FlowLayout(QLayout):
return len(self._item_list)
@override
def itemAt(self, index: int) -> QLayoutItem | None: # pyright: ignore[reportIncompatibleMethodOverride]
def itemAt(self, index: int) -> QLayoutItem | None:
if 0 <= index < len(self._item_list):
return self._item_list[index]
return None
@override
def takeAt(self, index: int) -> QLayoutItem | None: # pyright: ignore[reportIncompatibleMethodOverride]
def takeAt(self, index: int) -> QLayoutItem | None:
if 0 <= index < len(self._item_list):
return self._item_list.pop(index)
@@ -27,7 +27,7 @@ class ThumbGridLayout(QLayout):
# Id of first visible entry
visible_changed = Signal(int)
def __init__(self, driver: "QtDriver", scroll_area: QScrollArea) -> None:
def __init__(self, driver: QtDriver, scroll_area: QScrollArea) -> None:
super().__init__(None)
self.driver: QtDriver = driver
self.scroll_area: QScrollArea = scroll_area
@@ -198,7 +198,7 @@ class ThumbGridLayout(QLayout):
return
per_row, width_offset, height_offset = self._size(rect.right())
view_height = self.parentWidget().parentWidget().height()
view_height = self.parentWidget().parentWidget().height() # pyright: ignore[reportOptionalMemberAccess]
offset = self.scroll_area.verticalScrollBar().value()
if self._scroll_to is not None:
try:
@@ -34,7 +34,7 @@ if TYPE_CHECKING:
# TODO: Use newer MVC style guidelines
class LibraryInfoWindowView(QWidget):
def __init__(self, library: "Library", driver: "QtDriver"):
def __init__(self, library: Library, driver: QtDriver):
super().__init__()
self.lib = library
self.driver = driver
+1 -1
View File
@@ -44,7 +44,7 @@ class PreviewThumbView(QWidget):
__should_render_on_resize: bool
__rendered_res: tuple[int, int]
def __init__(self, library: Library, driver: "QtDriver") -> None:
def __init__(self, library: Library, driver: QtDriver) -> None:
super().__init__()
self._driver = driver
+1 -1
View File
@@ -23,7 +23,7 @@ logger = structlog.get_logger(__name__)
class TagBoxWidgetView(FieldWidget):
__lib: Library
def __init__(self, title: str, driver: "QtDriver") -> None:
def __init__(self, title: str, driver: QtDriver) -> None:
super().__init__(title)
self.__lib = driver.lib
+1
View File
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-only
# pyright: reportPrivateUsage = false
# pyright: reportOptionalMemberAccess=false
from collections.abc import Callable
from typing import cast