[PATCH] qtcompat: coerce int to Qt.CheckState in QAbstractTableModel for PyQt6

21 views
Skip to first unread message

Matt Harbison

unread,
Mar 23, 2023, 11:47:06 AM3/23/23
to thg...@googlegroups.com
# HG changeset patch
# User Matt Harbison <matt_h...@yahoo.com>
# Date 1679585964 14400
# Thu Mar 23 11:39:24 2023 -0400
# Branch pytype
# Node ID 1b1033f7ee26b49b9e70b6f5778598540cbb7427
# Parent c095784e091adb6eef04e20481f76f3b3def140c
# EXP-Topic pytype_stubs
qtcompat: coerce int to Qt.CheckState in QAbstractTableModel for PyQt6

Some python callers already pass the correct type, and the enum is a simple int
on PyQt5. However, when the checkboxes in the status widget were clicked on
PyQt6, the state remained unchanged due comparision between the int value and
the enum value. (The menu items and spacebar toggling still worked.) Simiarly,
the changeset checkboxes could be cleared for email and phabricator, but an
exception would be raised when re-checking it because it assumes the revision
needs to be removed from the selected list when the comparison fails.

diff --git a/tortoisehg/hgqt/hgemail.py b/tortoisehg/hgqt/hgemail.py
--- a/tortoisehg/hgqt/hgemail.py
+++ b/tortoisehg/hgqt/hgemail.py
@@ -423,6 +423,8 @@

rev = self._revs[index.row()]
if index.column() == 0 and role == Qt.ItemDataRole.CheckStateRole:
+ value = qtlib.toCheckStateEnum(value)
+
origvalue = rev in self._selectedrevs
if value == Qt.CheckState.Checked:
self._selectedrevs.add(rev)
diff --git a/tortoisehg/hgqt/qtlib.py b/tortoisehg/hgqt/qtlib.py
--- a/tortoisehg/hgqt/qtlib.py
+++ b/tortoisehg/hgqt/qtlib.py
@@ -1646,3 +1646,13 @@
except AttributeError:
# qt < 5.15
return signalMapper.mapped[str]
+
+def toCheckStateEnum(value: Union[int, Qt.CheckState]) -> Qt.CheckState:
+ """Coerce a Qt.CheckState value to the corresponding enum value."""
+ if QT_VERSION >= 0x060000:
+ if isinstance(value, int):
+ return {
+ e.value: e for e in Qt.CheckState.__members__.values()
+ }[value]
+
+ return value
diff --git a/tortoisehg/hgqt/status.py b/tortoisehg/hgqt/status.py
--- a/tortoisehg/hgqt/status.py
+++ b/tortoisehg/hgqt/status.py
@@ -946,6 +946,8 @@
return False
if (index.column() == COL_PATH and role == Qt.ItemDataRole.CheckStateRole
and self.checkable):
+ value = qtlib.toCheckStateEnum(value)
+
if self.data(index, role) == value:
return True
if value not in (Qt.CheckState.Checked, Qt.CheckState.Unchecked):

Matt Harbison

unread,
Mar 23, 2023, 1:49:33 PM3/23/23
to TortoiseHg Developers
Ignore this one, it makes pytype unhappy.

Matt Harbison

unread,
Mar 23, 2023, 10:56:46 PM3/23/23
to thg...@googlegroups.com
# HG changeset patch
# User Matt Harbison <matt_h...@yahoo.com>
# Date 1679585964 14400
# Thu Mar 23 11:39:24 2023 -0400
# Branch pytype
# Node ID 6d4b646ab00c0bd4299146c74fbe9c3d53a3fd41
# Parent c095784e091adb6eef04e20481f76f3b3def140c
# EXP-Topic pytype_stubs
qtcompat: coerce int to Qt.CheckState in QAbstractTableModel for PyQt6

Some python callers already pass the correct type, and the enum is a simple int
on PyQt5. However, when the checkboxes in the status widget were clicked on
PyQt6, the state remained unchanged due comparision between the int value and
the enum value. (The menu items and spacebar toggling still worked.) Simiarly,
the changeset checkboxes could be cleared for email and phabricator, but an
exception would be raised when re-checking it because it assumes the revision
needs to be removed from the selected list when the comparison fails.

diff --git a/tortoisehg/hgqt/hgemail.py b/tortoisehg/hgqt/hgemail.py
--- a/tortoisehg/hgqt/hgemail.py
+++ b/tortoisehg/hgqt/hgemail.py
@@ -423,6 +423,8 @@

rev = self._revs[index.row()]
if index.column() == 0 and role == Qt.ItemDataRole.CheckStateRole:
+ value = qtlib.toCheckStateEnum(value)
+
origvalue = rev in self._selectedrevs
if value == Qt.CheckState.Checked:
self._selectedrevs.add(rev)
diff --git a/tortoisehg/hgqt/qtlib.py b/tortoisehg/hgqt/qtlib.py
--- a/tortoisehg/hgqt/qtlib.py
+++ b/tortoisehg/hgqt/qtlib.py
@@ -1646,3 +1646,15 @@
except AttributeError:
# qt < 5.15
return signalMapper.mapped[str]
+
+def toCheckStateEnum(value: Union[int, Qt.CheckState]) -> Qt.CheckState:
+ """Coerce a Qt.CheckState value to the corresponding enum value."""
+ if QT_VERSION >= 0x060000:
+ if isinstance(value, int):
+ # pytype: disable=attribute-error
+ return {
+ e.value: e for e in Qt.CheckState.__members__.values()
+ }[value]
+ # pytype: enable=attribute-error
+
+ return cast(Qt.CheckState, value)

Matt Harbison

unread,
Mar 23, 2023, 10:56:47 PM3/23/23
to thg...@googlegroups.com
# HG changeset patch
# User Matt Harbison <matt_h...@yahoo.com>
# Date 1679588731 14400
# Thu Mar 23 12:25:31 2023 -0400
# Branch pytype
# Node ID 7f0bcefe1f2ad3708d5812d0abb38cb7264d460b
# Parent 6d4b646ab00c0bd4299146c74fbe9c3d53a3fd41
# EXP-Topic pytype_stubs
run: fix interpolation of str into bytes when forking the process fails

The `_()` method in this module returns bytes.

diff --git a/tortoisehg/hgqt/run.py b/tortoisehg/hgqt/run.py
--- a/tortoisehg/hgqt/run.py
+++ b/tortoisehg/hgqt/run.py
@@ -173,7 +173,8 @@
try:
_forkbg(ui)
except OSError as inst:
- ui.warn(_('failed to fork GUI process: %s\n') % inst.strerror)
+ ui.warn(_('failed to fork GUI process: %s\n')
+ % hglib.fromunicode(inst.strerror))

# native window API can't be used after fork() on Mac OS X
if os.name == 'posix' and sys.platform != 'darwin':

Matt Harbison

unread,
Mar 23, 2023, 10:56:49 PM3/23/23
to thg...@googlegroups.com
# HG changeset patch
# User Matt Harbison <matt_h...@yahoo.com>
# Date 1679590882 14400
# Thu Mar 23 13:01:22 2023 -0400
# Branch pytype
# Node ID 8cf2b991616871f916a92e050061469eee82942a
# Parent 7f0bcefe1f2ad3708d5812d0abb38cb7264d460b
# EXP-Topic pytype_stubs
run: fix a crash because of a missing argument when displaying help topics

This could be crashed with `thg help revsets`, for example.

diff --git a/tortoisehg/hgqt/run.py b/tortoisehg/hgqt/run.py
--- a/tortoisehg/hgqt/run.py
+++ b/tortoisehg/hgqt/run.py
@@ -923,7 +923,7 @@
if not doc:
doc = _("(no help text available)")
if hasattr(doc, '__call__'):
- doc = doc()
+ doc = doc(ui)

ui.write(b"%s\n" % header)
ui.write(b"%s\n" % doc.rstrip())

Matt Harbison

unread,
Mar 23, 2023, 10:56:50 PM3/23/23
to thg...@googlegroups.com
# HG changeset patch
# User Matt Harbison <matt_h...@yahoo.com>
# Date 1679591482 14400
# Thu Mar 23 13:11:22 2023 -0400
# Branch pytype
# Node ID 37d4164d5e3b71fcad1f889dd7cb86ddcdb553af
# Parent 8cf2b991616871f916a92e050061469eee82942a
# EXP-Topic pytype_stubs
run: avoid passing None to error.CommandError to appease type checkers

I couldn't figure out how to call this code normally, so I manually raised
GetOptError to trigger it, and the help output is the same as passing None
(namely it says "no commands defined"). But passing None upset pytype due to
the first arg not being marked Optional, which it can't be in case the exception
is stringified.

diff --git a/tortoisehg/hgqt/run.py b/tortoisehg/hgqt/run.py
--- a/tortoisehg/hgqt/run.py
+++ b/tortoisehg/hgqt/run.py
@@ -294,7 +294,7 @@
try:
args = fancyopts.fancyopts(args, globalopts, options)
except getopt.GetoptError as inst:
- raise error.CommandError(None, stringutil.forcebytestr(inst))
+ raise error.CommandError(b"", stringutil.forcebytestr(inst))

if args:
alias, args = args[0], args[1:]

Matt Harbison

unread,
Mar 23, 2023, 10:56:52 PM3/23/23
to thg...@googlegroups.com
# HG changeset patch
# User Matt Harbison <matt_h...@yahoo.com>
# Date 1679596842 14400
# Thu Mar 23 14:40:42 2023 -0400
# Branch pytype
# Node ID a106d23620c8494d4bd99126f43e1319a5aac0a4
# Parent 37d4164d5e3b71fcad1f889dd7cb86ddcdb553af
# EXP-Topic pytype_stubs
typing: whitelist tortoisehg/hgqt/run.py

diff --git a/pytype.cfg b/pytype.cfg
--- a/pytype.cfg
+++ b/pytype.cfg
@@ -9,7 +9,6 @@
**/*_ui.py
i18n/msgfmt.py
tortoisehg/hgqt/hgconfig.py
- tortoisehg/hgqt/run.py
tortoisehg/hgqt/shellconf.py
tortoisehg/util/pipeui.py
TortoiseHgOverlayServer.py

Matt Harbison

unread,
Mar 23, 2023, 10:56:53 PM3/23/23
to thg...@googlegroups.com
# HG changeset patch
# User Matt Harbison <matt_h...@yahoo.com>
# Date 1679597143 14400
# Thu Mar 23 14:45:43 2023 -0400
# Branch pytype
# Node ID 3683c8a297580398f24016c2bd4a77a1b6e4357a
# Parent a106d23620c8494d4bd99126f43e1319a5aac0a4
# EXP-Topic pytype_stubs
typing: add a few hints to tortoisehg/hgqt/run.py to suppress a PyCharm warning

Specifically, this one for `help_(ui, None)` around line 306:

Expected type '{__ne__}', got 'None' instead

diff --git a/tortoisehg/hgqt/run.py b/tortoisehg/hgqt/run.py
--- a/tortoisehg/hgqt/run.py
+++ b/tortoisehg/hgqt/run.py
@@ -14,6 +14,10 @@
import sys
import subprocess

+from typing import (
+ Optional,
+)
+
from mercurial import (
cmdutil,
encoding,
@@ -801,7 +805,7 @@

### help management, adapted from mercurial.commands.help_()
@command(b'help', [], _('thg help [COMMAND]'))
-def help_(ui, name=None, with_version=False, **opts):
+def help_(ui, name: Optional[bytes]=None, with_version=False, **opts):
"""show help for a command, extension, or list of commands

With no arguments, print a list of commands and short help.
@@ -830,7 +834,7 @@
msg = _('use "thg -v help %s" to show global options') % name
option_lists.append((msg, ()))

- def helpcmd(name):
+ def helpcmd(name: bytes) -> None:
if with_version:
version(ui)
ui.write(b'\n')
@@ -910,7 +914,7 @@
if not ui.quiet:
addglobalopts(True)

- def helptopic(name):
+ def helptopic(name: bytes) -> None:
from mercurial import help
for topic in help.helptable:
names, header, doc = topic[0:3]

Matt Harbison

unread,
Mar 23, 2023, 10:56:54 PM3/23/23
to thg...@googlegroups.com
# HG changeset patch
# User Matt Harbison <matt_h...@yahoo.com>
# Date 1679620732 14400
# Thu Mar 23 21:18:52 2023 -0400
# Branch pytype
# Node ID 15860a12ca4dea203790d2ae533ca050dea16df9
# Parent 3683c8a297580398f24016c2bd4a77a1b6e4357a
# EXP-Topic pytype_stubs
typing: factor `pycompat.maplist(hglib.tounicode, ...)` into a typesafe utility

This allows running pytype after hg 0ab92dabea6e, which added typehints to
`pycompat.maplist()` to ensure the item sequence is compatible with the
conversion function. Whether it's a bug in the typehint spec, or in the
implementation of both pytype and PyCharm, both combine the overloads of
`hglib.tounicode()` to express it as `(Optional[bytes | str]) -> Optional[str]`.
So even when the argument at the call site is known to *not* contain None, they
both think the result is a `List[Optional[str]]`, and that breaks places that
aren't declared with `Optional`. I also fiddled with `Literal[]` + py3.8, but
that didn't seem to help either.

I opted to add the utility function and cast inside instead of casting at all of
the call sites because casting simply disables the checking. That means future
refactoring could change the type of the argument, and it wouldn't be caught.
With this, the precondition can still be tested.

diff --git a/tortoisehg/hgqt/bookmark.py b/tortoisehg/hgqt/bookmark.py
--- a/tortoisehg/hgqt/bookmark.py
+++ b/tortoisehg/hgqt/bookmark.py
@@ -9,6 +9,10 @@

import re

+from typing import (
+ List,
+)
+
from .qtcore import (
QPoint,
Qt,
@@ -138,8 +142,8 @@
def repo(self):
return self._repoagent.rawRepo()

- def _allBookmarks(self):
- return pycompat.maplist(hglib.tounicode, self.repo._bookmarks)
+ def _allBookmarks(self) -> List[str]:
+ return hglib.to_unicode_list(self.repo._bookmarks)

@pyqtSlot()
def refresh(self):
diff --git a/tortoisehg/hgqt/hgconfig.py b/tortoisehg/hgqt/hgconfig.py
--- a/tortoisehg/hgqt/hgconfig.py
+++ b/tortoisehg/hgqt/hgconfig.py
@@ -79,7 +79,7 @@
default = pycompat.maplist(hglib.fromunicode, default)
data = self._ui.configlist(hglib.fromunicode(section), hglib.fromunicode(name),
default=default)
- return pycompat.maplist(hglib.tounicode, data)
+ return hglib.to_unicode_list(data)

def configStringItems(self, section):
# type: (Text) -> List[Tuple[Text, Text]]
diff --git a/tortoisehg/hgqt/mq.py b/tortoisehg/hgqt/mq.py
--- a/tortoisehg/hgqt/mq.py
+++ b/tortoisehg/hgqt/mq.py
@@ -10,6 +10,10 @@
import os
import re

+from typing import (
+ List,
+)
+
from .qtcore import (
QAbstractListModel,
QByteArray,
@@ -416,7 +420,7 @@
self._repoagent = repoagent
self._repoagent.repositoryChanged.connect(self._updateCache)
self._series = []
- self._seriesguards = []
+ self._seriesguards: List[List[bytes]] = []
self._statusmap = {} # patch: applied/guarded/unguarded
self._buildCache()

@@ -508,11 +512,10 @@
return ''
return hglib.tounicode(self._series[index.row()])

- def patchGuards(self, index):
+ def patchGuards(self, index: QModelIndex) -> List[str]:
if not index.isValid():
return []
- return pycompat.maplist(hglib.tounicode,
- self._seriesguards[index.row()])
+ return hglib.to_unicode_list(self._seriesguards[index.row()])

def isApplied(self, index):
if not index.isValid():
diff --git a/tortoisehg/hgqt/qfold.py b/tortoisehg/hgqt/qfold.py
--- a/tortoisehg/hgqt/qfold.py
+++ b/tortoisehg/hgqt/qfold.py
@@ -6,7 +6,9 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.

-
+from typing import (
+ List,
+)

from .qtcore import (
QSettings,
@@ -151,8 +153,8 @@
return {'keep': self.keepchk.isChecked(),
'message': self.msgte.text()}

- def patches(self):
- return pycompat.maplist(hglib.tounicode, self.ulw.getPatchList())
+ def patches(self) -> List[str]:
+ return hglib.to_unicode_list(self.ulw.getPatchList())

def accept(self):
self._writesettings()
diff --git a/tortoisehg/hgqt/repotreemodel.py b/tortoisehg/hgqt/repotreemodel.py
--- a/tortoisehg/hgqt/repotreemodel.py
+++ b/tortoisehg/hgqt/repotreemodel.py
@@ -10,6 +10,7 @@
import os

from typing import (
+ List,
Optional,
)

@@ -378,7 +379,7 @@
return self._indexFromItem(self._activeRepoItem, column)

# TODO: rename loadSubrepos() and appendSubrepos() to scanRepo() ?
- def loadSubrepos(self, index):
+ def loadSubrepos(self, index: QModelIndex) -> List[str]:
"""Scan subrepos of the repo; returns list of invalid paths"""
item = index.internalPointer()
if (not isinstance(item, repotreeitem.RepoItem)
@@ -400,7 +401,7 @@
item._sharedpath = tmpitem._sharedpath
item._valid = tmpitem._valid
self._emitItemDataChanged(item)
- return pycompat.maplist(hglib.tounicode, invalidpaths)
+ return hglib.to_unicode_list(invalidpaths)

def updateCommonPaths(self, showShortPaths=None):
if showShortPaths is not None:
diff --git a/tortoisehg/util/hglib.py b/tortoisehg/util/hglib.py
--- a/tortoisehg/util/hglib.py
+++ b/tortoisehg/util/hglib.py
@@ -15,6 +15,13 @@
import sys
import time

+from typing import (
+ cast,
+ Iterable,
+ List,
+ Union,
+)
+
from hgext import mq as mqmod
from mercurial import (
cmdutil,
@@ -65,12 +72,9 @@
Any,
Callable,
Dict,
- Iterable,
- List,
Optional,
Text,
Tuple,
- Union,
overload,
)
from mercurial import (
@@ -136,6 +140,9 @@
pass
return s.decode(_fallbackencoding, 'replace')

+def to_unicode_list(seq: Iterable[Union[bytes, str]]) -> List[str]:
+ return cast(List[str], pycompat.maplist(tounicode, seq))
+
if TYPE_CHECKING:
@overload
def fromunicode(s, errors='strict'):

Matt Harbison

unread,
Mar 23, 2023, 10:56:55 PM3/23/23
to thg...@googlegroups.com
# HG changeset patch
# User Matt Harbison <matt_h...@yahoo.com>
# Date 1679620770 14400
# Thu Mar 23 21:19:30 2023 -0400
# Branch pytype
# Node ID 1d1967060b8d514f6ae0d4858b0398eb9a4e458d
# Parent 15860a12ca4dea203790d2ae533ca050dea16df9
# EXP-Topic pytype_stubs
typing: whitelist tortoisehg/hgqt/hgconfig.py

My assumption is this started working when the py2 conditionalizing was dropped
in d46a91d151c9.

diff --git a/pytype.cfg b/pytype.cfg
--- a/pytype.cfg
+++ b/pytype.cfg
@@ -8,7 +8,6 @@
**/test_*.py
**/*_ui.py
i18n/msgfmt.py
- tortoisehg/hgqt/hgconfig.py
tortoisehg/hgqt/shellconf.py
tortoisehg/util/pipeui.py
TortoiseHgOverlayServer.py

Yuya Nishihara

unread,
Mar 23, 2023, 11:50:40 PM3/23/23
to Matt Harbison, thg...@googlegroups.com
On Thu, 23 Mar 2023 22:56:40 -0400, Matt Harbison wrote:
> # HG changeset patch
> # User Matt Harbison <matt_h...@yahoo.com>
> # Date 1679585964 14400
> # Thu Mar 23 11:39:24 2023 -0400
> # Branch pytype
> # Node ID 6d4b646ab00c0bd4299146c74fbe9c3d53a3fd41
> # Parent c095784e091adb6eef04e20481f76f3b3def140c
> # EXP-Topic pytype_stubs
> qtcompat: coerce int to Qt.CheckState in QAbstractTableModel for PyQt6

> +def toCheckStateEnum(value: Union[int, Qt.CheckState]) -> Qt.CheckState:
> + """Coerce a Qt.CheckState value to the corresponding enum value."""
> + if QT_VERSION >= 0x060000:

Nit: better to check PYQT_VERSION since this isn't a Qt API change.

> + if isinstance(value, int):
> + # pytype: disable=attribute-error
> + return {
> + e.value: e for e in Qt.CheckState.__members__.values()
> + }[value]
> + # pytype: enable=attribute-error

Appears that the enum type supports iterator protocol, so this can be

next(e for e in Qt.CheckState if e.value == value)

https://docs.python.org/3/library/enum.html#enum.EnumType.__iter__

Yuya Nishihara

unread,
Mar 23, 2023, 11:50:42 PM3/23/23
to Matt Harbison, thg...@googlegroups.com
On Thu, 23 Mar 2023 22:56:41 -0400, Matt Harbison wrote:
> # HG changeset patch
> # User Matt Harbison <matt_h...@yahoo.com>
> # Date 1679588731 14400
> # Thu Mar 23 12:25:31 2023 -0400
> # Branch pytype
> # Node ID 7f0bcefe1f2ad3708d5812d0abb38cb7264d460b
> # Parent 6d4b646ab00c0bd4299146c74fbe9c3d53a3fd41
> # EXP-Topic pytype_stubs
> run: fix interpolation of str into bytes when forking the process fails

Queued 2, 3, 5-8 for stable, thanks.

Yuya Nishihara

unread,
Mar 23, 2023, 11:50:44 PM3/23/23
to Matt Harbison, thg...@googlegroups.com
On Thu, 23 Mar 2023 22:56:43 -0400, Matt Harbison wrote:
> # HG changeset patch
> # User Matt Harbison <matt_h...@yahoo.com>
> # Date 1679591482 14400
> # Thu Mar 23 13:11:22 2023 -0400
> # Branch pytype
> # Node ID 37d4164d5e3b71fcad1f889dd7cb86ddcdb553af
> # Parent 8cf2b991616871f916a92e050061469eee82942a
> # EXP-Topic pytype_stubs
> run: avoid passing None to error.CommandError to appease type checkers
>
> I couldn't figure out how to call this code normally, so I manually raised
> GetOptError to trigger it, and the help output is the same as passing None
> (namely it says "no commands defined"). But passing None upset pytype due to
> the first arg not being marked Optional, which it can't be in case the exception
> is stringified.

Maybe it's a type-hint bug in Mercurial? mercurial.dispatch._parse() also
uses None as a unknown command name.

Matt Harbison

unread,
Mar 24, 2023, 12:19:50 AM3/24/23
to TortoiseHg Developers
I was going to change it there too, but I think you're right-  error._tobytes() apparently only cares about args passed to the Exception constructor, so both the command and message fields are ignored on this class:

>>> bytes(error.CommandError(b'', b'2nd'))
b''
>>> error.CommandError(b'', b'2nd').args
()

Matt Harbison

unread,
Mar 24, 2023, 2:18:33 AM3/24/23
to thg...@googlegroups.com
# HG changeset patch
# User Matt Harbison <matt_h...@yahoo.com>
# Date 1679585964 14400
# Thu Mar 23 11:39:24 2023 -0400
# Branch stable
# Node ID ade7a0c02188ad482efbbfa02d1f6b34b93aa0da
# Parent 040763c684197298ddcbe0107787e14791239717
# EXP-Topic pytype_stubs
qtcompat: coerce int to Qt.CheckState in QAbstractTableModel for PyQt6

Some python callers already pass the correct type, and the enum is a simple int
on PyQt5. However, when the checkboxes in the status widget were clicked on
PyQt6, the state remained unchanged due comparision between the int value and
the enum value. (The menu items and spacebar toggling still worked.) Simiarly,
the changeset checkboxes could be cleared for email and phabricator, but an
exception would be raised when re-checking it because it assumes the revision
needs to be removed from the selected list when the comparison fails.

The PyQt5 type doesn't implement `__iter__` and the PyQt version check isn't
recognized by pytype, so disable attribute-error when converting.

diff --git a/tortoisehg/hgqt/hgemail.py b/tortoisehg/hgqt/hgemail.py
--- a/tortoisehg/hgqt/hgemail.py
+++ b/tortoisehg/hgqt/hgemail.py
@@ -423,6 +423,8 @@

rev = self._revs[index.row()]
if index.column() == 0 and role == Qt.ItemDataRole.CheckStateRole:
+ value = qtlib.toCheckStateEnum(value)
+
origvalue = rev in self._selectedrevs
if value == Qt.CheckState.Checked:
self._selectedrevs.add(rev)
diff --git a/tortoisehg/hgqt/qtlib.py b/tortoisehg/hgqt/qtlib.py
--- a/tortoisehg/hgqt/qtlib.py
+++ b/tortoisehg/hgqt/qtlib.py
@@ -1646,3 +1646,13 @@
except AttributeError:
# qt < 5.15
return signalMapper.mapped[str]
+
+def toCheckStateEnum(value: Union[int, Qt.CheckState]) -> Qt.CheckState:
+ """Coerce a Qt.CheckState value to the corresponding enum value."""
+ if PYQT_VERSION >= 0x060000:
+ if isinstance(value, int):
+ # pytype: disable=attribute-error
+ return next(e for e in Qt.CheckState if e.value == value)

Yuya Nishihara

unread,
Mar 24, 2023, 5:48:19 AM3/24/23
to Matt Harbison, thg...@googlegroups.com
On Fri, 24 Mar 2023 02:18:28 -0400, Matt Harbison wrote:
> # HG changeset patch
> # User Matt Harbison <matt_h...@yahoo.com>
> # Date 1679585964 14400
> # Thu Mar 23 11:39:24 2023 -0400
> # Branch stable
> # Node ID ade7a0c02188ad482efbbfa02d1f6b34b93aa0da
> # Parent 040763c684197298ddcbe0107787e14791239717
> # EXP-Topic pytype_stubs
> qtcompat: coerce int to Qt.CheckState in QAbstractTableModel for PyQt6

Queued for stable, thanks.
Reply all
Reply to author
Forward
0 new messages