--- a/eric7/Plugins/WizardPlugins/SetupWizard/SetupWizardDialog.py Fri Jun 24 10:19:17 2022 +0200 +++ b/eric7/Plugins/WizardPlugins/SetupWizard/SetupWizardDialog.py Sun Jun 26 15:01:48 2022 +0200 @@ -10,14 +10,15 @@ import os import datetime -from PyQt6.QtCore import pyqtSlot, Qt, QUrl +import trove_classifiers + +from PyQt6.QtCore import pyqtSlot, Qt from PyQt6.QtWidgets import ( QDialog, QDialogButtonBox, QTreeWidgetItem, QListWidgetItem ) -from PyQt6.QtNetwork import QNetworkRequest, QNetworkReply from EricWidgets.EricApplication import ericApp -from EricWidgets import EricMessageBox, EricFileDialog +from EricWidgets import EricFileDialog from EricWidgets.EricPathPicker import EricPathPickerModes from .Ui_SetupWizardDialog import Ui_SetupWizardDialog @@ -33,13 +34,12 @@ It displays a dialog for entering the parameters for the setup.py code generator. """ - ClassifiersUrl = "https://pypi.org/pypi?%3Aaction=list_classifiers" - def __init__(self, parent=None): """ Constructor - @param parent reference to the parent widget (QWidget) + @param parent reference to the parent widget + @type QWidget """ super().__init__(parent) self.setupUi(self) @@ -62,7 +62,7 @@ self.maintainerEmailEdit]: lineEdit.setStyleSheet(self.__mandatoryStyleSheet) - self.__loadClassifiersFromPyPI() + self.__populateClassifiers() self.__okButton = self.buttonBox.button( QDialogButtonBox.StandardButton.Ok) @@ -96,73 +96,9 @@ self.__okButton.setEnabled(enable) - def __loadClassifiersFromPyPI(self): - """ - Private method to populate the classifiers list with data retrieved - from PyPI. - """ - request = QNetworkRequest(QUrl(SetupWizardDialog.ClassifiersUrl)) - request.setAttribute( - QNetworkRequest.Attribute.CacheLoadControlAttribute, - QNetworkRequest.CacheLoadControl.AlwaysNetwork) - reply = ( - ericApp().getObject("UserInterface").networkAccessManager() - .get(request) - ) - reply.finished.connect(lambda: self.__classifiersDownloadDone(reply)) - self.__replies.append(reply) - - @pyqtSlot() - def __classifiersDownloadDone(self, reply): - """ - Private slot called, after the classifiers file has been downloaded - from the internet. - - @param reply reference to the network reply - @type QNetworkReply - """ - reply.deleteLater() - if reply in self.__replies: - self.__replies.remove(reply) - if reply.error() == QNetworkReply.NetworkError.NoError: - ioEncoding = Preferences.getSystem("IOEncoding") - lines = str(reply.readAll(), ioEncoding, 'replace').splitlines() - - self.__populateClassifiers(lines) - - reply.close() - - @pyqtSlot() - def on_localClassifiersButton_clicked(self): - """ - Private method to populate lists from the Trove list file. - - Note: The trove list file was created from querying - "https://pypi.org/pypi?%3Aaction=list_classifiers". - """ - filename = os.path.join(os.path.dirname(__file__), - "data", "trove_classifiers.txt") - try: - with open(filename, "r") as f: - lines = f.readlines() - except OSError as err: - EricMessageBox.warning( - self, - self.tr("Reading Trove Classifiers"), - self.tr("""<p>The Trove Classifiers file <b>{0}</b>""" - """ could not be read.</p><p>Reason: {1}</p>""") - .format(filename, str(err))) - return - - self.__populateClassifiers(lines) - - def __populateClassifiers(self, classifiers): + def __populateClassifiers(self): """ Private method to populate the classifiers. - - @param classifiers list of classifiers read from a local file or - retrieved from PyPI - @type list of str """ self.licenseClassifierComboBox.clear() self.classifiersList.clear() @@ -171,18 +107,17 @@ self.developmentStatusComboBox.addItem("", "") self.__classifiersDict = {} - for line in classifiers: - line = line.strip() - if line.startswith("License "): + for classifier in trove_classifiers.sorted_classifiers: + if classifier.startswith("License ::"): self.licenseClassifierComboBox.addItem( - "/".join(line.split(" :: ")[1:]), - line + "/".join(classifier.split(" :: ")[1:]), + classifier ) - elif line.startswith("Development Status "): + elif classifier.startswith("Development Status ::"): self.developmentStatusComboBox.addItem( - line.split(" :: ")[1], line) + classifier.split(" :: ")[1], classifier) else: - self.__addClassifierEntry(line) + self.__addClassifierEntry(classifier) self.__classifiersDict = {} self.licenseClassifierComboBox.setCurrentIndex( @@ -192,15 +127,16 @@ ) ) - def __addClassifierEntry(self, line): + def __addClassifierEntry(self, classifier): """ Private method to add a new entry to the list of trove classifiers. - @param line line containing the data for the entry (string) + @param classifier classifier containing the data for the entry + @type str """ itm = None pitm = None - dataList = line.split(" :: ") + dataList = classifier.split(" :: ") for index in range(len(dataList)): key = " :: ".join(dataList[:index + 1]) if key not in self.__classifiersDict: @@ -215,13 +151,14 @@ else: pitm = self.__classifiersDict[key] itm.setCheckState(0, Qt.CheckState.Unchecked) - itm.setData(0, Qt.ItemDataRole.UserRole, line) + itm.setData(0, Qt.ItemDataRole.UserRole, classifier) def __getLicenseText(self): """ Private method to get the license text. - @return license text (string) + @return license text + @rtype str """ if not self.licenseClassifierCheckBox.isChecked(): return self.licenseEdit.text() @@ -235,9 +172,12 @@ """ Public method to get the source code. - @param indLevel indentation level (int) - @param indString string used for indentation (space or tab) (string) - @return generated code (string) + @param indLevel indentation level + @type int + @param indString string used for indentation (space or tab) + @type str + @return generated code + @rtype str """ # Note: all paths are created with '/'; setup will do the right thing @@ -250,95 +190,95 @@ # now generate the code if self.introCheckBox.isChecked(): - code = "#!/usr/bin/env python3{0}".format(os.linesep) - code += "# -*- coding: utf-8 -*-{0}{0}".format(os.linesep) + sourceCode = "#!/usr/bin/env python3{0}".format(os.linesep) + sourceCode += "# -*- coding: utf-8 -*-{0}{0}".format(os.linesep) else: - code = "" + sourceCode = "" if self.metaDataCheckBox.isChecked(): - code += '# metadata{0}'.format(os.linesep) - code += '"{0}"{1}'.format( + sourceCode += '# metadata{0}'.format(os.linesep) + sourceCode += '"{0}"{1}'.format( self.summaryEdit.text() or "Setup routine", os.linesep ) - code += '__version__ = "{0}"{1}'.format( + sourceCode += '__version__ = "{0}"{1}'.format( self.versionEdit.text(), os.linesep) - code += '__license__ = "{0}"{1}'.format( + sourceCode += '__license__ = "{0}"{1}'.format( self.__getLicenseText(), os.linesep) - code += '__author__ = "{0}"{1}'.format( + sourceCode += '__author__ = "{0}"{1}'.format( self.authorEdit.text() or self.maintainerEdit.text(), os.linesep) - code += '__email__ = "{0}"{1}'.format( + sourceCode += '__email__ = "{0}"{1}'.format( self.authorEmailEdit.text() or self.maintainerEmailEdit.text(), os.linesep) - code += '__url__ = "{0}"{1}'.format( + sourceCode += '__url__ = "{0}"{1}'.format( self.homePageUrlEdit.text(), os.linesep) - code += '__date__ = "{0}"{1}'.format( + sourceCode += '__date__ = "{0}"{1}'.format( datetime.datetime.now().isoformat().split('.')[0], os.linesep) - code += '__prj__ = "{0}"{1}'.format( + sourceCode += '__prj__ = "{0}"{1}'.format( self.nameEdit.text(), os.linesep) - code += os.linesep + sourceCode += os.linesep if self.importCheckBox.isChecked(): additionalImport = ", find_packages" - code += "from setuptools import setup{0}{1}".format( + sourceCode += "from setuptools import setup{0}{1}".format( additionalImport, os.linesep) - if code: - code += "{0}{0}".format(os.linesep) + if sourceCode: + sourceCode += "{0}{0}".format(os.linesep) if self.descriptionFromFilesCheckBox.isChecked(): - code += 'def get_long_description():{0}'.format(os.linesep) - code += '{0}descr = []{1}'.format(istring, os.linesep) - code += '{0}for fname in "{1}":{2}'.format( + sourceCode += 'def get_long_description():{0}'.format(os.linesep) + sourceCode += '{0}descr = []{1}'.format(istring, os.linesep) + sourceCode += '{0}for fname in "{1}":{2}'.format( istring, '", "'.join(self.descriptionEdit.toPlainText().splitlines()), os.linesep) - code += '{0}{0}with open(fname) as f:{1}'.format( + sourceCode += '{0}{0}with open(fname) as f:{1}'.format( istring, os.linesep) - code += '{0}{0}{0}descr.append(f.read()){1}'.format( + sourceCode += '{0}{0}{0}descr.append(f.read()){1}'.format( istring, os.linesep) - code += '{0}return "\\n\\n".join(descr){1}'.format( + sourceCode += '{0}return "\\n\\n".join(descr){1}'.format( istring, os.linesep) - code += "{0}{0}".format(os.linesep) + sourceCode += "{0}{0}".format(os.linesep) - code += 'setup({0}'.format(os.linesep) - code += '{0}name="{1}",{2}'.format( + sourceCode += 'setup({0}'.format(os.linesep) + sourceCode += '{0}name="{1}",{2}'.format( istring, self.nameEdit.text(), os.linesep) - code += '{0}version="{1}",{2}'.format( + sourceCode += '{0}version="{1}",{2}'.format( istring, self.versionEdit.text(), os.linesep) if self.summaryEdit.text(): - code += '{0}description="{1}",{2}'.format( + sourceCode += '{0}description="{1}",{2}'.format( istring, self.summaryEdit.text(), os.linesep) if self.descriptionFromFilesCheckBox.isChecked(): - code += '{0}long_description=get_long_description(),{1}'.format( + sourceCode += '{0}long_description=get_long_description(),{1}'.format( istring, os.linesep) elif self.descriptionEdit.toPlainText(): - code += '{0}long_description="""{1}""",{2}'.format( + sourceCode += '{0}long_description="""{1}""",{2}'.format( istring, self.descriptionEdit.toPlainText(), os.linesep) if self.authorEdit.text(): - code += '{0}author="{1}",{2}'.format( + sourceCode += '{0}author="{1}",{2}'.format( istring, self.authorEdit.text(), os.linesep) - code += '{0}author_email="{1}",{2}'.format( + sourceCode += '{0}author_email="{1}",{2}'.format( istring, self.authorEmailEdit.text(), os.linesep) if self.maintainerEdit.text(): - code += '{0}maintainer="{1}",{2}'.format( + sourceCode += '{0}maintainer="{1}",{2}'.format( istring, self.maintainerEdit.text(), os.linesep) - code += '{0}maintainer_email="{1}",{2}'.format( + sourceCode += '{0}maintainer_email="{1}",{2}'.format( istring, self.maintainerEmailEdit.text(), os.linesep) - code += '{0}url="{1}",{2}'.format( + sourceCode += '{0}url="{1}",{2}'.format( istring, self.homePageUrlEdit.text(), os.linesep) if self.downloadUrlEdit.text(): - code += '{0}download_url="{1}",{2}'.format( + sourceCode += '{0}download_url="{1}",{2}'.format( istring, self.downloadUrlEdit.text(), os.linesep) classifiers = [] if not self.licenseClassifierCheckBox.isChecked(): - code += '{0}license="{1}",{2}'.format( + sourceCode += '{0}license="{1}",{2}'.format( istring, self.licenseEdit.text(), os.linesep) else: classifiers.append( @@ -347,12 +287,12 @@ platforms = self.platformsEdit.toPlainText().splitlines() if platforms: - code += '{0}platforms=[{1}'.format(istring, os.linesep) - code += '{0}"{1}"{2}'.format( + sourceCode += '{0}platforms=[{1}'.format(istring, os.linesep) + sourceCode += '{0}"{1}"{2}'.format( i1string, '",{0}{1}"'.format(os.linesep, i1string).join(platforms), os.linesep) - code += '{0}],{1}'.format(istring, os.linesep) + sourceCode += '{0}],{1}'.format(istring, os.linesep) if self.developmentStatusComboBox.currentIndex() != 0: classifiers.append( @@ -369,19 +309,19 @@ # cleanup classifiers list - remove all invalid entries classifiers = [c for c in classifiers if bool(c)] if classifiers: - code += '{0}classifiers=[{1}'.format(istring, os.linesep) - code += '{0}"{1}"{2}'.format( + sourceCode += '{0}classifiers=[{1}'.format(istring, os.linesep) + sourceCode += '{0}"{1}"{2}'.format( i1string, '",{0}{1}"'.format(os.linesep, i1string).join(classifiers), os.linesep) - code += '{0}],{1}'.format(istring, os.linesep) + sourceCode += '{0}],{1}'.format(istring, os.linesep) del classifiers if self.keywordsEdit.text(): - code += '{0}keywords="{1}",{2}'.format( + sourceCode += '{0}keywords="{1}",{2}'.format( istring, self.keywordsEdit.text(), os.linesep) - code += '{0}packages=find_packages('.format(istring) + sourceCode += '{0}packages=find_packages('.format(istring) src = Utilities.fromNativeSeparators( self.sourceDirectoryPicker.text()) excludePatterns = [] @@ -389,51 +329,51 @@ excludePatterns.append( self.excludePatternList.item(row).text()) if src: - code += '{0}{1}"{2}"'.format(os.linesep, i1string, src) + sourceCode += '{0}{1}"{2}"'.format(os.linesep, i1string, src) if excludePatterns: - code += ',' + sourceCode += ',' else: - code += '{0}{1}'.format(os.linesep, istring) + sourceCode += '{0}{1}'.format(os.linesep, istring) if excludePatterns: - code += '{0}{1}exclude=[{0}'.format(os.linesep, i1string) - code += '{0}"{1}"{2}'.format( + sourceCode += '{0}{1}exclude=[{0}'.format(os.linesep, i1string) + sourceCode += '{0}"{1}"{2}'.format( i2string, '",{0}{1}"'.format(os.linesep, i2string) .join(excludePatterns), os.linesep) - code += '{0}]{1}{2}'.format(i1string, os.linesep, istring) - code += '),{0}'.format(os.linesep) + sourceCode += '{0}]{1}{2}'.format(i1string, os.linesep, istring) + sourceCode += '),{0}'.format(os.linesep) if self.includePackageDataCheckBox.isChecked(): - code += '{0}include_package_data = True,{1}'.format( + sourceCode += '{0}include_package_data = True,{1}'.format( istring, os.linesep) modules = [] for row in range(self.modulesList.count()): modules.append(self.modulesList.item(row).text()) if modules: - code += '{0}py_modules=[{1}'.format(istring, os.linesep) - code += '{0}"{1}"{2}'.format( + sourceCode += '{0}py_modules=[{1}'.format(istring, os.linesep) + sourceCode += '{0}"{1}"{2}'.format( i1string, '",{0}{1}"'.format(os.linesep, i1string).join(modules), os.linesep) - code += '{0}],{1}'.format(istring, os.linesep) + sourceCode += '{0}],{1}'.format(istring, os.linesep) del modules scripts = [] for row in range(self.scriptsList.count()): scripts.append(self.scriptsList.item(row).text()) if scripts: - code += '{0}scripts=[{1}'.format(istring, os.linesep) - code += '{0}"{1}"{2}'.format( + sourceCode += '{0}scripts=[{1}'.format(istring, os.linesep) + sourceCode += '{0}"{1}"{2}'.format( i1string, '",{0}{1}"'.format(os.linesep, i1string).join(scripts), os.linesep) - code += '{0}],{1}'.format(istring, os.linesep) + sourceCode += '{0}],{1}'.format(istring, os.linesep) del scripts - code += "){0}".format(estring) - return code + sourceCode += "){0}".format(estring) + return sourceCode @pyqtSlot() def on_projectButton_clicked(self): @@ -471,7 +411,8 @@ """ Private method to get the start directory for selection dialogs. - @return start directory (string) + @return start directory + @rtype str """ return (Preferences.getMultiProject("Workspace") or Utilities.getHomeDir()) @@ -593,7 +534,8 @@ """ Private slot to handle a change of the exclude pattern text. - @param txt text of the line edit (string) + @param txt text of the line edit + @type str """ self.addExludePatternButton.setEnabled(bool(txt))