From abf213e5bd253900e34afa22506e54021d7df010 Mon Sep 17 00:00:00 2001 From: Julien Date: Mon, 2 Sep 2024 15:28:20 +0200 Subject: [PATCH 1/5] enchance(refacto): rename shortucts/shortcuts module and apply absolute import --- .../jobs/job_shortcuts.py | 2 +- .../shortcuts/__init__.py | 4 --- .../{shortcuts.py => shortcuts_handler.py} | 27 ++++++++++++------- tests/test_shortcuts.py | 3 ++- 4 files changed, 20 insertions(+), 16 deletions(-) rename qgis_deployment_toolbelt/shortcuts/{shortcuts.py => shortcuts_handler.py} (95%) diff --git a/qgis_deployment_toolbelt/jobs/job_shortcuts.py b/qgis_deployment_toolbelt/jobs/job_shortcuts.py index 82120538..cc66a6ff 100644 --- a/qgis_deployment_toolbelt/jobs/job_shortcuts.py +++ b/qgis_deployment_toolbelt/jobs/job_shortcuts.py @@ -19,7 +19,7 @@ from qgis_deployment_toolbelt.__about__ import __title__, __version__ from qgis_deployment_toolbelt.jobs.generic_job import GenericJob from qgis_deployment_toolbelt.profiles.qdt_profile import QdtProfile -from qgis_deployment_toolbelt.shortcuts import ApplicationShortcut +from qgis_deployment_toolbelt.shortcuts.shortcuts_handler import ApplicationShortcut # ############################################################################# # ########## Globals ############### diff --git a/qgis_deployment_toolbelt/shortcuts/__init__.py b/qgis_deployment_toolbelt/shortcuts/__init__.py index 817e06d5..e69de29b 100644 --- a/qgis_deployment_toolbelt/shortcuts/__init__.py +++ b/qgis_deployment_toolbelt/shortcuts/__init__.py @@ -1,4 +0,0 @@ -#! python3 # noqa: E265 - -# submodules -from .shortcuts import ApplicationShortcut # noqa: F401 diff --git a/qgis_deployment_toolbelt/shortcuts/shortcuts.py b/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py similarity index 95% rename from qgis_deployment_toolbelt/shortcuts/shortcuts.py rename to qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py index 00edbd88..0d36ac55 100644 --- a/qgis_deployment_toolbelt/shortcuts/shortcuts.py +++ b/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py @@ -56,19 +56,26 @@ def __init__( self, name: str, exec_path: str | Path, - exec_arguments: Iterable[str] = None, - description: str = None, - icon_path: str | Path = None, - work_dir: str | Path = None, + exec_arguments: Iterable[str] | None = None, + description: str | None = None, + icon_path: str | Path | None = None, + work_dir: str | Path | None = None, ): """Initialize a shortcut object. - :param str name: name of the shortcut that will be created - :param Union[str, Path] exec_path: path to the executable (which should exist) - :param Iterable[str] exec_arguments: list of arguments and options to pass to the executable, defaults to None - :param str description: shortcut description, defaults to None - :param Union[str, Path] icon_path: path to icon file, defaults to None - :param Union[str, Path] work_dir: current folder where to start the executable, defaults to None. In QDT, it's the profile folder. + Args: + name (str): name of the shortcut that will be created + exec_path (str | Path): path to the executable (which should exist) + exec_arguments (Iterable[str] | None, optional): list of arguments and \ + options to pass to the executable. Defaults to None. + description (str | None, optional): shortcut description. Defaults to None. + icon_path (str | Path | None, optional): path to icon file. Defaults to None. + work_dir (str | Path | None, optional): current folder where to start the \ + executable. Defaults to None. In QDT, it's the profile folder. + + Raises: + ValueError: if shortcut name contains invalid characters (depending on \ + operating system) """ # retrieve operating system specific configuration self.os_config = OSConfiguration.from_opersys() diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py index bc38df21..19549025 100644 --- a/tests/test_shortcuts.py +++ b/tests/test_shortcuts.py @@ -10,12 +10,13 @@ python -m unittest tests.test_shortcuts.TestShortcut.test_shortcut_creation """ +# standard lib import unittest from pathlib import Path from sys import platform as opersys # project -from qgis_deployment_toolbelt.shortcuts import ApplicationShortcut +from qgis_deployment_toolbelt.shortcuts.shortcuts_handler import ApplicationShortcut # ############################################################################ # ########## Classes ############# From 876c37385c5f094801d03dec9d05a153b329d4f6 Mon Sep 17 00:00:00 2001 From: Julien Date: Mon, 2 Sep 2024 15:39:12 +0200 Subject: [PATCH 2/5] enchance(refacto): improve docstrings and type hints consistency --- .../shortcuts/shortcuts_handler.py | 101 ++++++++---------- 1 file changed, 44 insertions(+), 57 deletions(-) diff --git a/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py b/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py index 0d36ac55..bf349a74 100644 --- a/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py +++ b/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py @@ -27,10 +27,7 @@ import pythoncom import win32com.client from win32comext.shell import shell, shellcon -elif opersys == "linux": - import distro -else: - pass + # package from qgis_deployment_toolbelt.__about__ import __title__, __version__ @@ -153,13 +150,20 @@ def create( self, desktop: bool = False, start_menu: bool = False, - ) -> tuple[str, str]: - """Creates Shortcut + ) -> tuple[Path | None, Path | None]: + """Creates shortcuts. + + Args: + desktop (bool, optional): True to generate a Desktop shortcut. \ + Defaults to False. + start_menu (bool, optional): True to generate a 'Start Menu' shortcut. \ + Defaults to False. - :param bool desktop: True to generate a Desktop shortcut, defaults to False - :param bool start_menu: True to generate a 'Start Menu' shortcut, defaults to False + Raises: + TypeError: if options are not booleans - :return Tuple[str, str]: desktop and startmenu path + Returns: + tuple[Path, Path]: desktop and startmenu path """ if isinstance(desktop, bool): self.desktop = desktop @@ -184,16 +188,18 @@ def create( elif opersys == "linux": return self.freedesktop_create() else: - pass + return (None, None) def check_exec_arguments( self, exec_arguments: Iterable[str] | None ) -> tuple[str] | None: """Check if exec_arguments are valid. - :param Union[Iterable[str], None] exec_arguments: input executable arguments to check + Args: + exec_arguments (Iterable[str] | None): input executable arguments to check - :return Union[Tuple[str], None]: tuple of arguments + Returns: + tuple[str] | None: tuple of arguments """ if not exec_arguments: return None @@ -203,9 +209,11 @@ def check_exec_arguments( def check_icon_path(self, icon_path: str | Path | None) -> Path | None: """Check icon path and return full path if it exists. - :param Union[str, Path] icon_path: input icon path to check + Args: + icon_path (str | Path | None): input icon path to check - :return Union[Path, None]: icon path as Path if str or Path, else None + Returns: + Path | None: icon path as Path if str or Path, else None """ if icon_path is None: logger.debug( @@ -230,9 +238,11 @@ def check_icon_path(self, icon_path: str | Path | None) -> Path | None: def check_work_dir(self, work_dir: str | Path | None) -> Path | None: """Check work dir and return full path if it exists. - :param Union[str, Path] work_dir: input work dir to check + Args: + work_dir (str | Path | None): input work dir to check - :return Union[Path, None]: work dir as Path if str or Path, else None + Returns: + Path | None: work dir as Path if str or Path, else None """ if not work_dir: return None @@ -288,7 +298,7 @@ def desktop_path(self) -> Path: return default_value @property - def homedir_path(self) -> Path: + def homedir_path(self) -> Path | None: """Return home directory. For Windows, note that we return `CSIDL_PROFILE`, not `CSIDL_APPDATA`, @@ -296,7 +306,8 @@ def homedir_path(self) -> Path: See: https://www.nirsoft.net/articles/find_special_folder_location.html TODO: evaluate use of platformdirs - :return Path: path to the user home + Returns: + Path | None: path to the user home """ if opersys == "win32": return Path(shell.SHGetFolderPath(0, shellcon.CSIDL_PROFILE, 0, 0)) @@ -319,30 +330,34 @@ def homedir_path(self) -> Path: return None @property - def startmenu_path(self) -> Path: + def startmenu_path(self) -> Path | None: """Return user Start Menu Programs folder. For Windows, note that we return `CSIDL_PROGRAMS` not `CSIDL_COMMON_PROGRAMS`. - :return Path: path to the Start Menu Programs folder + Returns: + Path: path to the Start Menu Programs folder """ if opersys == "win32": return Path(shell.SHGetFolderPath(0, shellcon.CSIDL_PROGRAMS, None, 0)) elif opersys == "linux": - return self.homedir_path / ".local/share/applications" + if isinstance(self.homedir_path, Path): + return self.homedir_path / ".local/share/applications" + return None elif opersys == "darwin": - return self.homedir_path / ".local/share/applications" + if isinstance(self.homedir_path, Path): + return self.homedir_path / ".local/share/applications" + return None else: logger.error(f"Unrecognized operating system: {opersys}.") return None # -- PRIVATE -------------------------------------------------------------- - def freedesktop_create(self) -> tuple[Path | None, Path | None]: """Creates shortcut on distributions using FreeDesktop. - :return: desktop and startmenu path - :rtype: Tuple[Union[Path, None], Union[Path, None]] + Returns: + tuple[Path | None, Path | None]: desktop and startmenu path """ # grab shortcut template depending if we are in frozen mode # (typically PyInstaller) or as "normal" Python @@ -413,7 +428,7 @@ def freedesktop_create(self) -> tuple[Path | None, Path | None]: shortcut_desktop_path = None # create required shortcut - if self.start_menu: + if self.start_menu and isinstance(self.startmenu_path, Path): self.startmenu_path.mkdir(parents=True, exist_ok=True) # create shortcut shortcut_start_menu_path = Path( @@ -434,8 +449,8 @@ def freedesktop_create(self) -> tuple[Path | None, Path | None]: def win32_create(self) -> tuple[Path | None, Path | None]: """Creates shortcut on Windows. - :return: desktop and startmenu path - :rtype: Tuple[Union[Path, None], Union[Path, None]] + Returns: + tuple[Path | None, Path | None]: desktop and startmenu path """ # variable _WSHELL = win32com.client.Dispatch("Wscript.Shell", pythoncom.CoInitialize()) @@ -471,7 +486,7 @@ def win32_create(self) -> tuple[Path | None, Path | None]: shortcut_desktop_path = None # start menu shortcut - if self.start_menu: + if self.start_menu and isinstance(self.startmenu_path, Path): shortcut_start_menu_path = ( self.startmenu_path / f"{self.name}{self.os_config.shortcut_extension}" ) @@ -494,31 +509,3 @@ def win32_create(self) -> tuple[Path | None, Path | None]: shortcut_start_menu_path = None return (shortcut_desktop_path, shortcut_start_menu_path) - - -# ############################################################################# -# ##### Stand alone program ######## -# ################################## - -if __name__ == "__main__": - """Standalone execution.""" - if opersys == "linux" and distro.name() in ("Ubuntu", "Kubuntu"): - qgis_shortcut = ApplicationShortcut( - "QDT", - exec_path="/home/jmo/.local/bin/qdeploy-toolbelt", - exec_arguments=[ - "-v", - ], - description="Launch QGIS Deployment Toolbelt with the scenario located " - "into the local repository.", - work_dir=Path(__file__).parent.parent, - ) - - print( - type(qgis_shortcut.homedir_path), - qgis_shortcut.homedir_path, - ) - print(type(qgis_shortcut.desktop_path), qgis_shortcut.desktop_path) - print(type(qgis_shortcut.startmenu_path), qgis_shortcut.startmenu_path) - - qgis_shortcut.create() From f6f1fbe2bfec34f16525a02fa39224e5dab8eaa7 Mon Sep 17 00:00:00 2001 From: Julien Date: Mon, 2 Sep 2024 16:02:01 +0200 Subject: [PATCH 3/5] fix(linter): use constants instaed of repeated vars --- qgis_deployment_toolbelt/constants.py | 4 ++++ .../shortcuts/shortcuts_handler.py | 20 ++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/qgis_deployment_toolbelt/constants.py b/qgis_deployment_toolbelt/constants.py index 365e432a..8801399d 100644 --- a/qgis_deployment_toolbelt/constants.py +++ b/qgis_deployment_toolbelt/constants.py @@ -37,6 +37,10 @@ # defaults DEFAULT_QDT_WORKING_FOLDER = Path.home().joinpath(".cache/qgis-deployment-toolbelt") +# QGIS executable filenames +QGIS_BIN_WINDOWS_FILENAME: str = "qgis-bin.exe" +QGIS_LTR_BIN_WINDOWS_FILENAME: str = "qgis-ltr-bin.exe" + # Operating systems SUPPORTED_OPERATING_SYSTEMS_CODENAMES: tuple[str, ...] = ("darwin", "linux", "win32") diff --git a/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py b/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py index bf349a74..336baffa 100644 --- a/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py +++ b/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py @@ -31,7 +31,11 @@ # package from qgis_deployment_toolbelt.__about__ import __title__, __version__ -from qgis_deployment_toolbelt.constants import OSConfiguration +from qgis_deployment_toolbelt.constants import ( + QGIS_BIN_WINDOWS_FILENAME, + QGIS_LTR_BIN_WINDOWS_FILENAME, + OSConfiguration, +) from qgis_deployment_toolbelt.utils.check_path import check_path from qgis_deployment_toolbelt.utils.slugger import sluggy @@ -91,25 +95,27 @@ def __init__( if not self.exec_path.exists(): # helper to handle common typo error on executable name on Windows if ( - self.exec_path.name.endswith("qgis-bin.exe") - and self.exec_path.with_name("qgis-ltr-bin.exe").exists() + self.exec_path.name.endswith(QGIS_BIN_WINDOWS_FILENAME) + and self.exec_path.with_name(QGIS_LTR_BIN_WINDOWS_FILENAME).exists() ): logger.warning( f"Executable set does not exist: {self.exec_path} " f"but {self.exec_path.with_name('qgis-ltr-bin.exe')} does, so " "this one will be used instead. Check and fix your scenario." ) - self.exec_path = self.exec_path.with_name("qgis-ltr-bin.exe") + self.exec_path = self.exec_path.with_name( + QGIS_LTR_BIN_WINDOWS_FILENAME + ) elif ( - self.exec_path.name.endswith("qgis-ltr-bin.exe") - and self.exec_path.with_name("qgis-bin.exe").exists() + self.exec_path.name.endswith(QGIS_LTR_BIN_WINDOWS_FILENAME) + and self.exec_path.with_name(QGIS_BIN_WINDOWS_FILENAME).exists() ): logger.warning( f"Executable set does not exist: {self.exec_path} " f"but {self.exec_path.with_name('qgis-bin.exe')} does, so " "this one will be used instead. Check and fix your scenario." ) - self.exec_path = self.exec_path.with_name("qgis-bin.exe") + self.exec_path = self.exec_path.with_name(QGIS_BIN_WINDOWS_FILENAME) else: logger.warning( f"Executable does not exist: {self.exec_path}. " From a9d4ac11f5679386a64bc605ce411b9aa4d49355 Mon Sep 17 00:00:00 2001 From: Julien Date: Mon, 2 Sep 2024 16:07:47 +0200 Subject: [PATCH 4/5] fix(linter): type hint should be str --- qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py b/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py index 336baffa..5e43a7e2 100644 --- a/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py +++ b/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py @@ -196,20 +196,19 @@ def create( else: return (None, None) - def check_exec_arguments( - self, exec_arguments: Iterable[str] | None - ) -> tuple[str] | None: + def check_exec_arguments(self, exec_arguments: Iterable[str] | None) -> str | None: """Check if exec_arguments are valid. Args: exec_arguments (Iterable[str] | None): input executable arguments to check Returns: - tuple[str] | None: tuple of arguments + str | None: str of arguments """ if not exec_arguments: return None - # store as path + + # iterate and separate with spaces return " ".join(exec_arguments) def check_icon_path(self, icon_path: str | Path | None) -> Path | None: From cb77ca9358d3328dcf0ca8b2698ca04ca030597d Mon Sep 17 00:00:00 2001 From: Julien Date: Mon, 2 Sep 2024 16:15:16 +0200 Subject: [PATCH 5/5] fix(linter): merge duplicate code --- qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py b/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py index 5e43a7e2..b2c21978 100644 --- a/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py +++ b/qgis_deployment_toolbelt/shortcuts/shortcuts_handler.py @@ -345,11 +345,7 @@ def startmenu_path(self) -> Path | None: """ if opersys == "win32": return Path(shell.SHGetFolderPath(0, shellcon.CSIDL_PROGRAMS, None, 0)) - elif opersys == "linux": - if isinstance(self.homedir_path, Path): - return self.homedir_path / ".local/share/applications" - return None - elif opersys == "darwin": + elif opersys in ("darwin", "linux"): if isinstance(self.homedir_path, Path): return self.homedir_path / ".local/share/applications" return None