Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/add-new-language.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Set up python
uses: actions/setup-python@v5
with:
python-version: '3.11.9'
python-version: '3.13'
architecture: 'x86'

- name: setup uv
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/fetch-crowdin-translations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Set up python
uses: actions/setup-python@v5
with:
python-version: '3.11.9'
python-version: '3.13'
architecture: 'x86'

- name: setup uv
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Set up python
uses: actions/setup-python@v5
with:
python-version: '3.11.9'
python-version: '3.13'
architecture: 'x86'

- name: Install dependencies
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/testAndPublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ env:
SCONS_CACHE_MSVC_CONFIG: ".scons_msvc_cache.json"
defaultArch: x86
supportedArchitectures: '["x86"]'
defaultPythonVersion: '3.11.9'
supportedPythonVersions: '["3.11.9", "3.13.6"]'
defaultPythonVersion: '3.13.6'
supportedPythonVersions: '["3.13.6"]'

jobs:
matrix:
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ ci:
submodules: true

default_language_version:
python: python3.11
python: python3.13

repos:
- repo: https://github.com/pre-commit-ci/pre-commit-ci-config
Expand Down Expand Up @@ -121,7 +121,7 @@ repos:
- id: uv-lock
name: Verify uv lock file
# Override python interpreter from .python-versions as that is too strict for pre-commit.ci
args: ["-p3.11"]
args: ["-p3.13"]

- repo: local
hooks:
Expand Down
1 change: 0 additions & 1 deletion .python-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
cpython-3.11.9-windows-x86-none
cpython-3.13.6-windows-x86-none
2 changes: 1 addition & 1 deletion projectDocs/dev/createDevEnvironment.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The following dependencies need to be installed on your system:

#### Python

[Python](https://www.python.org/), version 3.11.9, 32 bit.
[Python](https://www.python.org/), version 3.13.6, 32 bit.
Install the python version listed in [.python-versions](../../.python-versions)

#### uv
Expand Down
4 changes: 1 addition & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description = "NonVisual Desktop Access (NVDA)"
maintainers = [
{name = "NV Access", email = "[email protected]"},
]
requires-python = ">=3.11,<3.14"
requires-python = ">=3.13,<3.14"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: End Users/Desktop",
Expand All @@ -33,8 +33,6 @@ dependencies = [
"schedule==1.2.2",
# NVDA_DMP requires diff-match-patch
"fast-diff-match-patch==2.1.0",
# typing_extensions are required for specifying default value for `TypeVar` prior to Python 3.13 (see PEP 696)
"typing-extensions==4.12.2",
# pycaw is a Core Audio Windows Library used for sound split
"pycaw==20240210",
# Sanitize HTML for browsable messages and documentation output to prevent XSS from translators
Expand Down
64 changes: 29 additions & 35 deletions source/UIAHandler/_remoteOps/remoteFuncWrapper.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
# Copyright (C) 2023-2024 NV Access Limited
# Copyright (C) 2023-2025 NV Access Limited


from __future__ import annotations
from collections.abc import Callable
from typing import (
Generator,
ContextManager,
Callable,
Concatenate,
ParamSpec,
TypeVar,
)
import functools
import contextlib
from . import builder


_remoteFunc_self = TypeVar("_remoteFunc_self", bound=builder._RemoteBase)
_remoteFunc_paramSpec = ParamSpec("_remoteFunc_paramSpec")
_remoteFunc_return = TypeVar("_remoteFunc_return")
_remoteFunc_self = builder._RemoteBase


class _BaseRemoteFuncWrapper:
Expand All @@ -29,13 +23,13 @@ def generateArgsKwargsString(self, *args, **kwargs) -> str:
kwargsString = ", ".join(f"{key}={repr(val)}" for key, val in kwargs.items())
return f"({', '.join([argsString, kwargsString])})"

def _execRawFunc(
def _execRawFunc[**P, R](
self,
func: Callable[Concatenate[_remoteFunc_self, _remoteFunc_paramSpec], _remoteFunc_return],
func: Callable[Concatenate[_remoteFunc_self, P], R],
funcSelf: _remoteFunc_self,
*args: _remoteFunc_paramSpec.args,
**kwargs: _remoteFunc_paramSpec.kwargs,
) -> _remoteFunc_return:
*args: P.args,
**kwargs: P.kwargs,
) -> R:
main = funcSelf.rob.getInstructionList("main")
main.addComment(
f"Entering {func.__qualname__}{self.generateArgsKwargsString(*args, **kwargs)}",
Expand All @@ -44,16 +38,16 @@ def _execRawFunc(
main.addComment(f"Exiting {func.__qualname__}")
return res

def __call__(
def __call__[**P, R](
self,
func: Callable[Concatenate[_remoteFunc_self, _remoteFunc_paramSpec], _remoteFunc_return],
) -> Callable[Concatenate[_remoteFunc_self, _remoteFunc_paramSpec], _remoteFunc_return]:
func: Callable[Concatenate[_remoteFunc_self, P], R],
) -> Callable[Concatenate[_remoteFunc_self, P], R]:
@functools.wraps(func)
def wrapper(
funcSelf: _remoteFunc_self,
*args: _remoteFunc_paramSpec.args,
**kwargs: _remoteFunc_paramSpec.kwargs,
) -> _remoteFunc_return:
*args: P.args,
**kwargs: P.kwargs,
) -> R:
return self._execRawFunc(func, funcSelf, *args, **kwargs)

return wrapper
Expand All @@ -65,40 +59,40 @@ class RemoteMethodWrapper(_BaseRemoteFuncWrapper):
def __init__(self, mutable: bool = False):
self._mutable = mutable

def _execRawFunc(
def _execRawFunc[**P, R](
self,
func: Callable[Concatenate[_remoteFunc_self, _remoteFunc_paramSpec], _remoteFunc_return],
func: Callable[Concatenate[_remoteFunc_self, P], R],
funcSelf: _remoteFunc_self,
*args: _remoteFunc_paramSpec.args,
**kwargs: _remoteFunc_paramSpec.kwargs,
) -> _remoteFunc_return:
*args: P.args,
**kwargs: P.kwargs,
) -> R:
if self._mutable and not funcSelf._mutable:
raise RuntimeError(f"{funcSelf.__class__.__name__} is not mutable")
return super()._execRawFunc(func, funcSelf, *args, **kwargs)


class RemoteContextManager(_BaseRemoteFuncWrapper):
def __call__(
def __call__[**P, R](
self,
func: Callable[
Concatenate[_remoteFunc_self, _remoteFunc_paramSpec],
Generator[_remoteFunc_return, None, None],
Concatenate[_remoteFunc_self, P],
Generator[R, None, None],
],
) -> Callable[Concatenate[_remoteFunc_self, _remoteFunc_paramSpec], ContextManager[_remoteFunc_return]]:
) -> Callable[Concatenate[_remoteFunc_self, P], ContextManager[R]]:
contextFunc = contextlib.contextmanager(func)
return super().__call__(contextFunc)

@contextlib.contextmanager
def _execRawFunc(
def _execRawFunc[**P, R](
self,
func: Callable[
Concatenate[_remoteFunc_self, _remoteFunc_paramSpec],
ContextManager[_remoteFunc_return],
Concatenate[_remoteFunc_self, P],
ContextManager[R],
],
funcSelf: _remoteFunc_self,
*args: _remoteFunc_paramSpec.args,
**kwargs: _remoteFunc_paramSpec.kwargs,
) -> Generator[_remoteFunc_return, None, None]:
*args: P.args,
**kwargs: P.kwargs,
) -> Generator[R, None, None]:
main = funcSelf.rob.getInstructionList("main")
main.addComment(
f"Entering context manager {func.__qualname__}{self.generateArgsKwargsString(*args, **kwargs)}",
Expand Down
9 changes: 1 addition & 8 deletions source/UIAHandler/_remoteOps/remoteTypes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
# Copyright (C) 2023-2024 NV Access Limited
# Copyright (C) 2023-2025 NV Access Limited


from __future__ import annotations
from typing import (
Type,
Any,
Self,
ParamSpec,
Iterable,
Generic,
TypeVar,
Expand Down Expand Up @@ -40,11 +38,6 @@
from .. import operation


_remoteFunc_self = TypeVar("_remoteFunc_self", bound=builder._RemoteBase)
_remoteFunc_paramSpec = ParamSpec("_remoteFunc_paramSpec")
_remoteFunc_return = TypeVar("_remoteFunc_return")


LocalTypeVar = TypeVar("LocalTypeVar")


Expand Down
16 changes: 12 additions & 4 deletions source/gui/addonStoreGui/viewModels/addonList.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

from abc import abstractmethod
from dataclasses import dataclass
from enum import Enum

from locale import strxfrm
from typing import (
Any,
FrozenSet,
Generic,
List,
Optional,
TYPE_CHECKING,
Protocol,
TypeVar,
cast,
)

from requests.structures import CaseInsensitiveDict
Expand All @@ -35,10 +39,12 @@


if TYPE_CHECKING:
# Remove when https://github.com/python/typing/issues/760 is resolved
from _typeshed import SupportsLessThan # noqa: F401
from .store import AddonStoreVM

class _SupportsLessThan(Protocol):
@abstractmethod
def __lt__(self, other: Any) -> bool: ...


@dataclass
class _AddonListFieldData:
Expand Down Expand Up @@ -405,13 +411,15 @@ def _columnSortChoices(self) -> list[str]:
)
return columnChoices

def _getFilteredSortedIds(self) -> List[str]:
def _getSortFieldData(listItemVM: AddonListItemVM) -> "SupportsLessThan":
def _getFilteredSortedIds(self) -> list[str]:
def _getSortFieldData(listItemVM: AddonListItemVM) -> _SupportsLessThan:
if self._sortByModelField == AddonListField.publicationDate:
if getattr(listItemVM.model, "submissionTime", None):
listItemVM.model = cast(_AddonStoreModel, listItemVM.model)
return listItemVM.model.submissionTime
return 0
if self._sortByModelField == AddonListField.installDate:
listItemVM.model = cast(_AddonManifestModel, listItemVM.model)
return listItemVM.model.installDate
return strxfrm(self._getAddonFieldText(listItemVM, self._sortByModelField))

Expand Down
24 changes: 7 additions & 17 deletions source/gui/guiHelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ def __init__(self, parent):
Any,
Generic,
Optional,
ParamSpec,
Type,
TypeVar,
Union,
Expand Down Expand Up @@ -484,16 +483,11 @@ class SIPABCMeta(wx.siplib.wrappertype, ABCMeta):
pass


# TODO: Rewrite to use type parameter lists when upgrading to python 3.12 or later.
_WxCallOnMain_P = ParamSpec("_WxCallOnMain_P")
_WxCallOnMain_T = TypeVar("_WxCallOnMain_T")


def wxCallOnMain(
function: Callable[_WxCallOnMain_P, _WxCallOnMain_T],
*args: _WxCallOnMain_P.args,
**kwargs: _WxCallOnMain_P.kwargs,
) -> _WxCallOnMain_T:
def wxCallOnMain[**P, T](
function: Callable[P, T],
*args: P.args,
**kwargs: P.kwargs,
) -> T:
"""Call a non-thread-safe wx function in a thread-safe way.
Blocks current thread.

Expand Down Expand Up @@ -532,11 +526,7 @@ def functionWrapper():
return result


# TODO: Rewrite to use type parameter lists when upgrading to python 3.12 or later.
_AlwaysCallAfterP = ParamSpec("_AlwaysCallAfterP")


def alwaysCallAfter(func: Callable[_AlwaysCallAfterP, Any]) -> Callable[_AlwaysCallAfterP, None]:
def alwaysCallAfter[**P](func: Callable[P, Any]) -> Callable[P, None]:
"""Makes GUI updates thread-safe by running in the main thread.

Example:
Expand All @@ -549,7 +539,7 @@ def updateLabel(text):
"""

@wraps(func)
def wrapper(*args: _AlwaysCallAfterP.args, **kwargs: _AlwaysCallAfterP.kwargs) -> None:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> None:
wx.CallAfter(func, *args, **kwargs)

return wrapper
8 changes: 3 additions & 5 deletions source/gui/message.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- coding: UTF-8 -*-
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2025 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Mesar Hameed, Joseph Lee,
# Thomas Stivers, Babbage B.V., Accessolutions, Julien Cochuyt
Expand All @@ -14,7 +13,7 @@
from collections.abc import Callable, Collection
from enum import Enum, IntEnum, auto
from functools import partialmethod, singledispatchmethod, wraps
from typing import Any, Literal, NamedTuple, Optional, Self, TypeAlias
from typing import Any, Literal, NamedTuple, Optional, Self

import core
import extensionPoints
Expand Down Expand Up @@ -177,8 +176,7 @@ class Payload:
"""Payload of information to pass to message dialog callbacks."""


# TODO: Change to type statement when Python 3.12 or later is in use.
_Callback_T: TypeAlias = Callable[[Payload], Any]
type _Callback_T = Callable[[Payload], Any]


class _Missing_Type:
Expand Down Expand Up @@ -222,7 +220,7 @@ class EscapeCode(IntEnum):
"""


wxArtID: TypeAlias = int
type wxArtID = int


class DialogType(Enum):
Expand Down
Loading
Loading