Source code for ssst._utilities

import enum
import os
import pathlib
import subprocess
import sys
import sysconfig
import typing

import qts

import ssst
import ssst.exceptions


qt_api_variable_name = "QTS_WRAPPER"
"""The name of the environment variable qts checks for selecting the desired
Qt wrapper API."""


[docs]class QtApis(enum.Enum): """Supported Qt APIs. Values correspond to qts names for each. Generally used as a parameter to :func:`ssst._utilities.configure_qt_wrapper()`. """ PyQt5 = "pyqt5" """PyQt5 by Riverbank Computing""" PySide2 = "pyside2" """PySide2 by Qt"""
[docs]def configure_qt_wrapper(api: QtApis) -> None: """Set the configuration such that qts will use the specified Qt wrapper API. Args: api: The Qt wrapper API for qts to use. """ if qts.wrapper is not None: raise ssst.exceptions.QtWrapperError(f"qts already configured: {qts.wrapper}") if qt_api_variable_name not in os.environ: os.environ[qt_api_variable_name] = api.value qts.set_wrapper(qts.wrapper_by_name(api.value)) else: qts.autoset_wrapper()
[docs]def scripts_path() -> pathlib.Path: """Get the path where console and GUI scripts are created. For example, in Linux this may be ``env/bin/`` and on Windows perhaps ``env/scripts/``. Uses :func:`sysconfig.get_path`. Returns: The scripts directory path. Raises: ssst.InternalError: if :func:`sysconfig.get_path` returns :obj:`None`. """ maybe_scripts_path_string = sysconfig.get_path("scripts") if maybe_scripts_path_string is None: # pragma: no cover raise ssst.InternalError("No scripts path defined.") return pathlib.Path(maybe_scripts_path_string)
[docs]def script_path(name: str) -> pathlib.Path: """Get the path to a console or GUI script of the given name. This does include the ``.exe`` extension on Windows. Arguments: name: The name of the script to get the path for. Returns: The path to the script. Raises: ssst.InternalError: see :func:`ssst._utilities.scripts_path`. """ base_path = scripts_path().joinpath(name) if sys.platform == "win32": full_path = base_path.with_suffix(".exe") else: full_path = base_path return full_path
[docs]def compile_ui( directory_path: typing.Sequence[pathlib.Path] = ( pathlib.Path(ssst.__file__).parent, ), output: typing.Optional[typing.Callable[..., object]] = None, ) -> None: """Compile the specified Qt UI files to Python source that can be imported and used. The files are found by searching the passed directory path for ``*.ui`` files which are compiled to ``*_ui.py`` within the same directory. Arguments: directory_path """ generic_compile_ui( directory_paths=directory_path, output=output, )
# TODO: This .ui building bit was 'extracted' from alqtendpy and likely belongs not # here. # https://github.com/altendky/ssst/issues/15
[docs]def generic_compile_ui( file_paths: typing.Sequence[pathlib.Path] = (), directory_paths: typing.Sequence[pathlib.Path] = (), extension: str = ".ui", suffix: str = "_ui", encoding: str = "utf-8", output: typing.Optional[typing.Callable[..., object]] = None, ) -> None: """Compile the specified Qt UI files to Python source. In addition to the passed file paths, the passed directory paths are searched to find additional files to compile. The search is done based on the passed extension. Taking the default ``".ui"`` extension and ``"_ui"`` suffix for the sake of an example, an input file such as ``x.ui`` will be output as ``x_ui.py`` in the same directory. The passed encoding is used when writing the Python file. If not :obj:`None`, the passed output callable will be used like :func:`print` to produce any output. Arguments: file_paths: Paths to individual UI files to be compiled. directory_paths: Paths of directories to be searched for UI files to compile. extension: The extension used to identify UI files when searching. suffix: The suffix to add to the stem of the UI file when creating the Python file names. encoding: The encoding to use when writing the Python files. output: The function to call when there is output that would normally be printed. """ paths = [ *(pathlib.Path(path) for path in file_paths), *( path for directory in directory_paths for path in pathlib.Path(directory).rglob("*" + extension) ), ] compile_paths( ui_paths=paths, suffix=suffix, encoding=encoding, output=output, )
def _do_nothing(*args: object, **kwargs: object) -> None: """Accept any arguments and do nothing. This exists for use as a default for :func:`ssst._utilities.compile_ui`."""
[docs]def compile_paths( ui_paths: typing.Sequence[pathlib.Path], suffix: str = "_ui", encoding: str = "utf-8", output: typing.Optional[typing.Callable[..., object]] = None, ) -> None: """Compile the passed UI paths to Python files. The passed suffix will be added to the stem of each file compiled and the extension changed to ``.py`` to create the output file name within the same directory. The passed encoding will be used when writing the Python file. Arguments: ui_paths: The UI files to compile to Python. suffix: The suffix to add to the stem when creating the Python file name. encoding: The encoding to use when writing the Python file. output: The :func:`print`-alike output function to call. :obj:`None` results in no output. Raises: ssst.QtWrapperError: If the ``qts`` module is not already imported and the import has not been forced. """ if output is None: output = _do_nothing if qts.wrapper is None: raise ssst.QtWrapperError( "qts is expected to be configured before calling this function.", ) for in_path in ui_paths: out_path = in_path.with_name(f"{in_path.stem}{suffix}.py") output(f"Converting: {in_path} -> {out_path}") script_name = { qts.pyqt_5_wrapper: "pyuic5", qts.pyside_5_wrapper: "pyside2-uic", }[qts.wrapper] completed_process = subprocess.run( [os.fspath(script_path(name=script_name)), os.fspath(in_path)], check=True, stdout=subprocess.PIPE, ) intermediate = completed_process.stdout.decode("utf-8") intermediate = intermediate.replace( f"from {qts.wrapper.module_name}", "from qts" ) out_path.write_text(intermediate, encoding=encoding)