Source code for qts._core

import importlib.util
import os
import sys
import typing

import attr

import qts


[docs]@attr.frozen class Wrapper: """A representation of a specific wrapper that can be used to access a specific version of Qt. """ family: str """The wrapper family. ``"PyQt"`` or ``"PySide"``.""" name: str """The name of the specific wrapper, including the version number. Such as ``"PySide6"``.""" major_version: int """The major version of the wrapped Qt library. Such as ``6``.""" module_name: str """The name used to import the module. Such as ``"PySide6"``."""
pyqt_5_wrapper = Wrapper( family="PyQt", name="PyQt5", major_version=5, module_name="PyQt5" ) """The PyQt/Qt5 wrapper object.""" pyqt_6_wrapper = Wrapper( family="PyQt", name="PyQt6", major_version=6, module_name="PyQt6" ) """The PyQt/Qt6 wrapper object.""" pyside_5_wrapper = Wrapper( family="PySide", name="PySide2", major_version=5, module_name="PySide2" ) """The PySide/Qt5 wrapper object.""" pyside_6_wrapper = Wrapper( family="PySide", name="PySide6", major_version=6, module_name="PySide6" ) """The PySide/Qt6 wrapper object.""" supported_wrappers = [ pyside_6_wrapper, pyqt_6_wrapper, pyside_5_wrapper, pyqt_5_wrapper, ] """A list of all the supported wrapper objects.""" _wrappers_by_name = {wrapper.name.casefold(): wrapper for wrapper in supported_wrappers}
[docs]def set_wrapper(wrapper: Wrapper) -> None: """Set the wrapper you want to back the Qt modules accessed through qts. :raises qts.WrapperAlreadySelectedError: When called and a wrapper has already been set. :raises qts.InvalidWrapperError: When called with an invalid wrapper. :raises qts.OtherWrapperAlreadyImportedError: When another supported wrapper has already been imported. """ # This could accept the new wrapper if it matches the existing selection, but this # seems like it would mostly just encourage coding that hazards setting to a # different wrapper in some other case. May as well complain early so that # developers get early warning and can adjust their design. if qts.wrapper is not None: raise qts.WrapperAlreadySelectedError( existing_wrapper=qts.wrapper, requested_wrapper=wrapper, ) if wrapper not in supported_wrappers: raise qts.InvalidWrapperError(wrapper=wrapper) already_imported = check_already_imported_wrappers() if len(already_imported) > 0 and wrapper not in already_imported: raise qts.OtherWrapperAlreadyImportedError( requested=wrapper, already_imported=already_imported ) qts.wrapper = wrapper qts.is_pyqt_5_wrapper = wrapper == pyqt_5_wrapper qts.is_pyqt_6_wrapper = wrapper == pyqt_6_wrapper qts.is_pyside_5_wrapper = wrapper == pyside_5_wrapper qts.is_pyside_6_wrapper = wrapper == pyside_6_wrapper
def already_imported_wrapper_names() -> typing.List[str]: return [ module for module in sys.modules if "." not in module if any(module.startswith(name) for name in ["PyQt", "PySide"]) ] def check_already_imported_wrappers( wrappers: typing.Optional[typing.Iterable[Wrapper]] = None, ) -> typing.List[Wrapper]: """Checks for wrappers that have already been imported and returns any that are supported. If only unsupported wrappers have been imported then an exception is raised. :param wrappers: An iterable of :class:`qts.Wrapper` to use as the supported list. If unspecified or :object:`None` then :attr:`qts.supported_wrappers` is used. :returns: A list of the supported wrappers that have already been imported. :raises qts.UnsupportedWrappersError: When only unsupported wrappers have been imported. """ if wrappers is None: wrappers = supported_wrappers already_imported_names = already_imported_wrapper_names() if len(already_imported_names) == 0: return [] supported = {wrapper.module_name for wrapper in wrappers} supported_already_imported = supported.intersection(already_imported_names) if len(supported_already_imported) == 0: raise qts.UnsupportedWrappersError(module_names=already_imported_names) return [ wrapper_by_name(name=module_name) for module_name in supported_already_imported ]
[docs]def autoset_wrapper() -> None: """Automatically choose and set the wrapper used to back the Qt modules accessed through qts. If the environment variable ``QTS_WRAPPER`` is set to a name of a supported wrapper then that wrapper will be used. The lookup is case insensitive. If a supported wrapper has already been imported then it will be used. :raises qts.InvalidWrapperError: When an unsupported wrapper name is specified in the ``QTS_WRAPPER`` environment variable. """ environment_wrapper_name = os.environ.get("QTS_WRAPPER") if environment_wrapper_name is not None: environment_wrapper = _wrappers_by_name.get(environment_wrapper_name.casefold()) if environment_wrapper is None: raise qts.InvalidWrapperError(wrapper=environment_wrapper) else: set_wrapper(wrapper=environment_wrapper) return already_imported = check_already_imported_wrappers() if len(already_imported) > 0: available = an_available_wrapper(wrappers=already_imported) else: available = an_available_wrapper() set_wrapper(wrapper=available)
[docs]def available_wrappers( wrappers: typing.Optional[typing.Iterable[Wrapper]] = None, ) -> typing.Sequence[Wrapper]: """Get a sequence of the wrappers that are available for use. If ``wrappers`` is passed, only wrappers that are both available and in the passed iterable will be returned. Availability is checked both by installation metadata and any wrappers that have already been imported. :returns: The wrappers that are installed and available for use. """ if wrappers is None: wrappers = supported_wrappers already_imported_names = already_imported_wrapper_names() available = [ wrapper for wrapper in wrappers if ( importlib.util.find_spec(wrapper.module_name) or wrapper.name in already_imported_names ) ] return available
[docs]def available_wrapper( wrappers: typing.Optional[typing.Iterable[Wrapper]] = None, ) -> Wrapper: """Get the available wrapper when there is only one. :return: The wrapper object for the single available wrapper. :raises qts.NoWrapperAvailableError: When no wrappers are available. :raises qts.MultipleWrappersAvailableError: If more than one wrapper is available. """ if wrappers is None: wrappers = supported_wrappers all_available = available_wrappers(wrappers=wrappers) if len(all_available) == 0: raise qts.NoWrapperAvailableError(wrappers=wrappers) elif len(all_available) > 1: raise qts.MultipleWrappersAvailableError(searched=wrappers, found=all_available) [the_one] = all_available return the_one
[docs]def an_available_wrapper( wrappers: typing.Optional[typing.Iterable[Wrapper]] = None, ) -> Wrapper: """Get an available wrapper when there is one or more available. :param wrappers: The wrappers to consider. All if not specified. :return: The wrapper object for the single available wrapper. :raises qts.NoWrapperAvailableError: When no wrappers are available. """ if wrappers is None: wrappers = supported_wrappers all_available = available_wrappers(wrappers=wrappers) for wrapper in wrappers: if wrapper in all_available: return wrapper raise qts.NoWrapperAvailableError(wrappers=wrappers)
[docs]def wrapper_by_name(name: str) -> Wrapper: """Get a wrapper object by its name. The name is checked case insensitively. :returns: The wrapper that goes by the passed name. """ return _wrappers_by_name[name.casefold()]