From edc71f12a323b81c6caf34623d84d175a815a275 Mon Sep 17 00:00:00 2001 From: "j.spijker@ultimaker.com" Date: Wed, 3 Nov 2021 13:43:08 +0100 Subject: [PATCH] Updated documentation and typing Contributes to CURA-8558 --- plugins/Marketplace/LocalPackageList.py | 35 ++++++++++++++++------- plugins/Marketplace/Marketplace.py | 2 +- plugins/Marketplace/PackageList.py | 38 +++++++++++++------------ 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index e672c191f6..aad1bbbaa1 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -2,10 +2,9 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtSlot, Qt -from typing import TYPE_CHECKING +from typing import Any, Dict, Generator, TYPE_CHECKING from UM.i18n import i18nCatalog -from UM.Logger import Logger from cura.CuraApplication import CuraApplication @@ -31,7 +30,7 @@ class LocalPackageList(PackageList): "plugin": catalog.i18nc("@label:property", "Bundled Plugins"), "material": catalog.i18nc("@label:property", "Bundled Materials") } - } + } # The section headers to be used for the different package categories def __init__(self, parent: "QObject" = None) -> None: super().__init__(parent) @@ -40,42 +39,58 @@ class LocalPackageList(PackageList): @pyqtSlot() def updatePackages(self) -> None: - """ - Make a request for the first paginated page of packages. - - When the request is done, the list will get updated with the new package models. + """Update the list with local packages, these are materials or plugin, either bundled or user installed. The list + will also contain **to be removed** or **to be installed** packages since the user might still want to interact + with these. """ self.setErrorMessage("") # Clear any previous errors. self.setIsLoading(True) self._getLocalPackages() + self.setIsLoading(True) def _getLocalPackages(self) -> None: + """ Obtain the local packages. + The list is sorted per category as in the order of the PACKAGE_SECTION_HEADER dictionary, whereas the packages + for the sections are sorted alphabetically on the display name + """ + sorted_sections = {} + # Filter the package list per section_title and sort these for section in self._getSections(): packages = filter(lambda p: p["section_title"] == section, self._allPackageInfo()) sorted_sections[section] = sorted(packages, key = lambda p: p["display_name"]) + # Create a PackageModel from the sorted package_info and append them to the list for section in sorted_sections.values(): for package_data in section: package = PackageModel(package_data, parent = self) self.appendItem({"package": package}) self.setIsLoading(False) - self.setHasMore(False) + self.setHasMore(False) # All packages should have been loaded at this time - def _getSections(self): + def _getSections(self) -> Generator[str]: + """ Flatten and order the PACKAGE_SECTION_HEADER such that it can be used in obtaining the packages in the + correct order""" for package_type in self.PACKAGE_SECTION_HEADER.values(): for section in package_type.values(): yield section - def _allPackageInfo(self): + def _allPackageInfo(self) -> Generator[Dict[str, Any]]: + """ A generator which returns a unordered list of package_info, the section_title is appended to the each + package_info""" + manager = self._application.getPackageManager() + + # Get all the installed packages, add a section_title depending on package_type and user installed for package_type, packages in manager.getAllInstalledPackagesInfo().items(): for package_data in packages: bundled_or_installed = "installed" if manager.isUserInstalledPackage(package_data["package_id"]) else "bundled" package_data["section_title"] = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type] yield package_data + # Get all to be removed package_info's. These packages are still used in the current session so the user might + # to interact with these in the list for package_data in manager.getPackagesToRemove().values(): yield package_data["package_info"] diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 9418174d19..18d80d6e68 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -9,8 +9,8 @@ from typing import Optional, TYPE_CHECKING from cura.ApplicationMetadata import CuraSDKVersion from cura.CuraApplication import CuraApplication # Creating QML objects and managing packages. from cura.UltimakerCloud import UltimakerCloudConstants + from UM.Extension import Extension # We are implementing the main object of an extension here. -from UM.Logger import Logger from UM.PluginRegistry import PluginRegistry # To find out where we are stored (the proper way). from .RemotePackageList import RemotePackageList # To register this type with QML. diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index de07e5e2fb..17a755255b 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -14,6 +14,9 @@ catalog = i18nCatalog("cura") class PackageList(ListModel): + """ A List model for Packages, this class serves as parent class for more detailed implementations. + such as Packages obtained from Remote or Local source + """ PackageRole = Qt.UserRole + 1 def __init__(self, parent: "QObject" = None) -> None: @@ -26,20 +29,20 @@ class PackageList(ListModel): @pyqtSlot() def updatePackages(self) -> None: - """ - Initialize the first page of packages - """ - self.setErrorMessage("") # Clear any previous errors. - self.isLoadingChanged.emit() + """ A Qt slot which will update the List from a source. Actual implementation should be done in the child class""" + pass @pyqtSlot() def abortUpdating(self) -> None: + """ A Qt slot which allows the update process to be aborted. Override this for child classes with async/callback + updatePackges methods""" pass def reset(self) -> None: + """ Resets and clears the list""" self.clear() - isLoadingChanged = pyqtSignal() + isLoadingChanged = pyqtSignal() # The signal for isLoading property def setIsLoading(self, value: bool) -> None: if self._is_loading != value: @@ -48,13 +51,12 @@ class PackageList(ListModel): @pyqtProperty(bool, fset = setIsLoading, notify = isLoadingChanged) def isLoading(self) -> bool: - """ - Gives whether the list is currently loading the first page or loading more pages. - :return: ``True`` if the list is being gathered, or ``False`` if . + """ Indicating if the the packages are loading + :return" ``True`` if the list is being obtained, otherwise ``False`` """ return self._is_loading - hasMoreChanged = pyqtSignal() + hasMoreChanged = pyqtSignal() # The signal for hasMore property def setHasMore(self, value: bool) -> None: if self._has_more != value: @@ -63,24 +65,21 @@ class PackageList(ListModel): @pyqtProperty(bool, fset = setHasMore, notify = hasMoreChanged) def hasMore(self) -> bool: - """ - Returns whether there are more packages to load. - :return: ``True`` if there are more packages to load, or ``False`` if we've reached the last page of the - pagination. + """ Indicating if there are more packages available to load. + :return: ``True`` if there are more packages to load, or ``False``. """ return self._has_more + errorMessageChanged = pyqtSignal() # The signal for errorMessage property + def setErrorMessage(self, error_message: str) -> None: if self._error_message != error_message: self._error_message = error_message self.errorMessageChanged.emit() - errorMessageChanged = pyqtSignal() - @pyqtProperty(str, notify = errorMessageChanged, fset = setErrorMessage) def errorMessage(self) -> str: - """ - If an error occurred getting the list of packages, an error message will be held here. + """ If an error occurred getting the list of packages, an error message will be held here. If no error occurred (yet), this will be an empty string. :return: An error message, if any, or an empty string if everything went okay. @@ -89,4 +88,7 @@ class PackageList(ListModel): @pyqtProperty(bool, constant = True) def hasFooter(self) -> bool: + """ Indicating if the PackageList should have a Footer visible. For paginated PackageLists + :return: ``True`` if a Footer should be displayed in the ListView, e.q.: paginated lists, ``False`` Otherwise + """ return self._has_footer