Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
18 changes: 1 addition & 17 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,22 +84,6 @@ jobs:
- name: Configure poetry
run: poetry config virtualenvs.in-project true

- name: Setup cache
uses: actions/cache@v4
id: cache
with:
path: .venv
key: venv-${{ runner.os }}-${{ matrix.qt-version }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }}

- name: Valdate cache
if: steps.cache.outputs.cache-hit == 'true'
run: |
# `timeout` is not available on macos, so we define a custom function.
[ "$(command -v timeout)" ] || function timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; }

# Using `timeout` is a safeguard against the Poetry command hanging for some reason.
timeout 10s poetry run pip --version || rm -rf .venv

- name: Check lock file
run: poetry check --lock

Expand All @@ -110,7 +94,7 @@ jobs:
# run: poetry run mypy

- name: Install Qt
run: poetry run pip install --ignore-installed ${{ matrix.qt-version }}
run: poetry run pip install ${{ matrix.qt-version }}

- name: Install libxcb dependencies
if: ${{ matrix.os == 'ubuntu' }}
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ _build
.cache
*.so

# coverage
.coverage.*.*.*

# logs
pip-log.txt

Expand Down
169 changes: 162 additions & 7 deletions poetry.lock

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ classifiers = [

[tool.poetry.dependencies]
python = ">=3.8, <3.14"
coverage-conditional-plugin = ">=0.9.0" # known to work with this version

[tool.poetry.group.dev.dependencies]
pre-commit = "^2.21"
Expand Down Expand Up @@ -65,3 +66,19 @@ build-backend = "poetry.core.masonry.api"
addopts = "-n auto"
markers = ["raises"]
testpaths = ["tests"]

[tool.coverage.run]
plugins = ["coverage_conditional_plugin"]

[tool.coverage.coverage_conditional_plugin.omit]
"sys_platform == 'win32'" = "qasync/_unix.py"
"sys_platform != 'win32'" = "qasync/_windows.py"

[tool.coverage.coverage_conditional_plugin.rules]
no_cover_if_windows = "sys_platform == 'win32'"
no_cover_if_unix = "sys_platform != 'win32'"
no_cover_if_lt312 = "sys_version_info < (3, 12, 0)"
no_cover_if_geq312 = "sys_version_info >= (3, 12, 0)"

[tool.coverage.report]
show_missing = true
58 changes: 19 additions & 39 deletions qasync/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

QtModule = None


# If QT_API env variable is given, use that or fail trying
qtapi_env = os.getenv("QT_API", "").strip().lower()
if qtapi_env:
Expand All @@ -38,27 +39,27 @@
"pyside2": "PySide2",
"pyside": "PySide6",
}
if qtapi_env in env_to_mod_map:
try:
QtModuleName = env_to_mod_map[qtapi_env]
else:
except KeyError as e: # pragma: no cover

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW: I made this use try except for an extremely minor speed up — you don’t need to check for existness in the dict first when it exists — and from my experience it’s more pythonic to ask for forgiveness like this.

raise ImportError(
"QT_API environment variable set ({}) but not one of [{}].".format(
qtapi_env, ", ".join(env_to_mod_map.keys())
)
)
) from e

logger.info("Forcing use of {} as Qt Implementation".format(QtModuleName))
QtModule = importlib.import_module(QtModuleName)

# If a Qt lib is already imported, use that
if not QtModule:
if not QtModule: # pragma: no cover
for QtModuleName in ("PyQt5", "PyQt6", "PySide2", "PySide6"):
if QtModuleName in sys.modules:
QtModule = sys.modules[QtModuleName]
break

# Try importing qt libs
if not QtModule:
if not QtModule: # pragma: no cover
for QtModuleName in ("PyQt5", "PyQt6", "PySide2", "PySide6"):
try:
QtModule = importlib.import_module(QtModuleName)
Expand All @@ -67,39 +68,16 @@
else:
break

if not QtModule:
if not QtModule: # pragma: no cover
raise ImportError("No Qt implementations found")

QtCore = importlib.import_module(QtModuleName + ".QtCore", package=QtModuleName)
QtGui = importlib.import_module(QtModuleName + ".QtGui", package=QtModuleName)

if QtModuleName == "PyQt5":
from PyQt5 import QtWidgets
from PyQt5.QtCore import pyqtSlot as Slot

QApplication = QtWidgets.QApplication
AllEvents = QtCore.QEventLoop.ProcessEventsFlags(0x00)

elif QtModuleName == "PyQt6":
from PyQt6 import QtWidgets
from PyQt6.QtCore import pyqtSlot as Slot

QApplication = QtWidgets.QApplication
AllEvents = QtCore.QEventLoop.ProcessEventsFlag(0x00)

elif QtModuleName == "PySide2":
from PySide2 import QtWidgets
from PySide2.QtCore import Slot

QApplication = QtWidgets.QApplication
AllEvents = QtCore.QEventLoop.ProcessEventsFlags(0x00)

elif QtModuleName == "PySide6":
from PySide6 import QtWidgets
from PySide6.QtCore import Slot

QApplication = QtWidgets.QApplication
AllEvents = QtCore.QEventLoop.ProcessEventsFlags(0x00)
QtWidgets = importlib.import_module(QtModuleName + ".QtWidgets", package=QtModuleName)
QApplication = QtWidgets.QApplication
Slot = getattr(QtCore, "pyqtSlot", None) or getattr(QtCore, "Slot")
ProcessEventsFlag = getattr(QtCore.QEventLoop, "ProcessEventsFlag", None) or getattr(QtCore.QEventLoop, "ProcessEventsFlags", None)
AllEvents = ProcessEventsFlag(0x00)

from ._common import with_logger # noqa

Expand Down Expand Up @@ -180,7 +158,9 @@ def __init__(self, max_workers=10, stack_size=None):
super().__init__()
self.__max_workers = max_workers
self.__queue = Queue()
if stack_size is None:
# No cover the whole thing because we cannot test
# some of the configurations.
if stack_size is None: # pragma: no cover
# Match cpython/Python/thread_pthread.h
if sys.platform.startswith("darwin"):
stack_size = 16 * 2**20
Expand Down Expand Up @@ -776,12 +756,12 @@ def __log_error(cls, *args, **kwds):

QSelectorEventLoop = type("QSelectorEventLoop", (_QEventLoop, _SelectorEventLoop), {})

if os.name == "nt":
if os.name == "nt": # pragma: no_cover_if_unix
from ._windows import _ProactorEventLoop

QIOCPEventLoop = type("QIOCPEventLoop", (_QEventLoop, _ProactorEventLoop), {})
QEventLoop = QIOCPEventLoop
else:
else: # pragma: no_cover_if_windows
QEventLoop = QSelectorEventLoop


Expand Down Expand Up @@ -853,15 +833,15 @@ def _get_qevent_loop():
return QEventLoop(QApplication.instance() or QApplication(sys.argv))


if sys.version_info >= (3, 12):
if sys.version_info >= (3, 12): # pragma: no_cover_if_lt312

def run(*args, **kwargs):
return asyncio.run(
*args,
**kwargs,
loop_factory=_get_qevent_loop,
)
else:
else: # pragma: no_cover_if_geq312
# backwards compatibility with event loop policies
class DefaultQEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
def new_event_loop(self):
Expand Down
4 changes: 3 additions & 1 deletion qasync/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ def with_logger(cls):
module = cls.__module__
if module is not None:
cls_name = module + "." + cls_name
else:
# This is just an internal helper function; edge cases
# like this need not be tested, so no cover.
else: # pragma: no cover
raise AssertionError
setattr(cls, attr_name, logging.getLogger(cls_name))
return cls
7 changes: 0 additions & 7 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@
level=logging.DEBUG, format="%(levelname)s\t%(filename)s:%(lineno)s %(message)s"
)


if os.name == "nt":
collect_ignore = ["qasync/_unix.py"]
else:
collect_ignore = ["qasync/_windows.py"]
Comment thread
johnzhou721 marked this conversation as resolved.


@fixture(scope="session")
def application():
from qasync import QApplication
Expand Down
Loading