Fri, 04 Nov 2022 13:52:26 +0100
Resorted the import statements using isort.
9453 | 1 | # -*- coding: utf-8 -*- |
2 | ||
3 | # Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de> | |
4 | # | |
5 | ||
6 | """ | |
7 | Module implementing a dialog showing the isort code formatting progress and the results. | |
8 | """ | |
9 | ||
10 | import copy | |
11 | import io | |
12 | import multiprocessing | |
13 | import pathlib | |
14 | ||
15 | from dataclasses import dataclass | |
16 | ||
9468
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
17 | from isort import settings |
9453 | 18 | from isort.api import check_file, sort_file |
9473
3f23dbf37dbe
Resorted the import statements using isort.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9472
diff
changeset
|
19 | from PyQt6.QtCore import QCoreApplication, Qt, pyqtSlot |
9453 | 20 | from PyQt6.QtWidgets import ( |
21 | QAbstractButton, | |
22 | QDialog, | |
23 | QDialogButtonBox, | |
24 | QHeaderView, | |
25 | QTreeWidgetItem, | |
26 | ) | |
27 | ||
28 | from eric7 import Preferences | |
29 | from eric7.EricWidgets import EricMessageBox | |
30 | ||
31 | from .FormattingDiffWidget import FormattingDiffWidget | |
32 | from .IsortFormattingAction import IsortFormattingAction | |
33 | from .IsortUtilities import suppressStderr | |
34 | from .Ui_IsortFormattingDialog import Ui_IsortFormattingDialog | |
35 | ||
36 | ||
37 | class IsortFormattingDialog(QDialog, Ui_IsortFormattingDialog): | |
38 | """ | |
39 | Class implementing a dialog showing the isort code formatting progress and the | |
40 | results. | |
41 | """ | |
42 | ||
9460
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
43 | DataRole = Qt.ItemDataRole.UserRole |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
44 | DataTypeRole = Qt.ItemDataRole.UserRole + 1 |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
45 | FileNameRole = Qt.ItemDataRole.UserRole + 2 |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
46 | StatusRole = Qt.ItemDataRole.UserRole + 3 |
9453 | 47 | |
9460
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
48 | FileNameColumn = 1 |
9453 | 49 | StatusColumn = 0 |
50 | ||
51 | def __init__( | |
52 | self, | |
53 | configuration, | |
54 | filesList, | |
55 | project=None, | |
56 | action=IsortFormattingAction.Sort, | |
57 | parent=None, | |
58 | ): | |
59 | """ | |
60 | Constructor | |
61 | ||
62 | @param configuration dictionary containing the configuration parameters | |
63 | @type dict | |
64 | @param filesList list of absolute file paths to be processed | |
65 | @type list of str | |
66 | @param project reference to the project object (defaults to None) | |
67 | @type Project (optional) | |
68 | @param action action to be performed (defaults to IsortFormattingAction.Sort) | |
69 | @type IsortFormattingAction (optional) | |
70 | @param parent reference to the parent widget (defaults to None) | |
71 | @type QWidget (optional) | |
72 | """ | |
73 | super().__init__(parent) | |
74 | self.setupUi(self) | |
75 | ||
76 | self.resultsList.header().setSortIndicator(1, Qt.SortOrder.AscendingOrder) | |
77 | ||
9465
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
78 | self.__project = project |
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
79 | |
9453 | 80 | self.__config = copy.deepcopy(configuration) |
81 | self.__config["quiet"] = True # we don't want extra output | |
82 | self.__config["overwrite_in_place"] = True # we want to overwrite the files | |
83 | if "config_source" in self.__config: | |
84 | del self.__config["config_source"] | |
9465
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
85 | |
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
86 | # Create an isort Config object and pre-load it with parameters contained in |
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
87 | # project specific configuration files (like pyproject.toml). The configuration |
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
88 | # given as a dictionary (i.e. data entered in the configuration dialog) |
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
89 | # overwrites these. If the project is not passed, the isort config is based on |
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
90 | # the isort defaults. |
9468
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
91 | if project: |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
92 | # clear the caches in order to force a re-read of config files |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
93 | settings._get_config_data.cache_clear() |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
94 | settings._find_config.cache_clear() |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
95 | try: |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
96 | self.__isortConfig = ( |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
97 | settings.Config(settings_path=project.getProjectPath(), **self.__config) |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
98 | if project |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
99 | else settings.Config(**self.__config) |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
100 | ) |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
101 | except KeyError: |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
102 | # invalid configuration entry found in some config file; use just the dialog |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
103 | # parameters |
a4d8091cd8f7
Changed the isort formatting dialog to ensure the cached isort config data is cleared.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9465
diff
changeset
|
104 | self.__isortConfig = settings.Config(**self.__config) |
9465
8a020c34dce2
Fine tuned the isort support so the configuration will default to project specific config files overwritten by data entered via the dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9460
diff
changeset
|
105 | |
9453 | 106 | self.__config["__action__"] = action # needed by the workers |
107 | ||
108 | self.__filesList = filesList[:] | |
109 | ||
110 | self.__diffDialog = None | |
111 | ||
112 | self.__allFilter = self.tr("<all>") | |
113 | ||
114 | self.__sortImportsButton = self.buttonBox.addButton( | |
115 | self.tr("Sort Imports"), QDialogButtonBox.ButtonRole.ActionRole | |
116 | ) | |
117 | self.__sortImportsButton.setVisible(False) | |
118 | ||
119 | self.show() | |
120 | QCoreApplication.processEvents() | |
121 | ||
122 | self.__performAction() | |
123 | ||
124 | def __performAction(self): | |
125 | """ | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
126 | Private method to execute the requested sorting action. |
9453 | 127 | """ |
9472
5798ee4a8807
Corrected a little glitch in the isort formatting dialog.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9468
diff
changeset
|
128 | self.progressBar.setMaximum(len(self.__filesList)) |
9453 | 129 | self.progressBar.setValue(0) |
130 | self.progressBar.setVisible(True) | |
131 | ||
132 | self.statisticsGroup.setVisible(False) | |
133 | self.__statistics = IsortStatistics() | |
134 | ||
135 | self.__cancelled = False | |
136 | ||
137 | self.statusFilterComboBox.clear() | |
138 | self.resultsList.clear() | |
139 | ||
140 | self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True) | |
141 | self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) | |
142 | self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) | |
143 | ||
144 | files = self.__filterFiles(self.__filesList) | |
145 | if len(files) > 1: | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
146 | self.__sortManyFiles(files) |
9453 | 147 | elif len(files) == 1: |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
148 | self.__sortOneFile(files[0]) |
9453 | 149 | |
150 | def __filterFiles(self, filesList): | |
151 | """ | |
152 | Private method to filter the given list of files according the | |
153 | configuration parameters. | |
154 | ||
155 | @param filesList list of files | |
156 | @type list of str | |
157 | @return list of filtered files | |
158 | @rtype list of str | |
159 | """ | |
160 | files = [] | |
161 | for file in filesList: | |
162 | if not self.__isortConfig.is_supported_filetype( | |
163 | file | |
164 | ) or self.__isortConfig.is_skipped(pathlib.Path(file)): | |
165 | self.__handleIsortResult(file, "skipped") | |
166 | else: | |
167 | files.append(file) | |
168 | ||
169 | return files | |
170 | ||
171 | def __resort(self): | |
172 | """ | |
173 | Private method to resort the result list. | |
174 | """ | |
175 | self.resultsList.sortItems( | |
176 | self.resultsList.sortColumn(), | |
177 | self.resultsList.header().sortIndicatorOrder(), | |
178 | ) | |
179 | ||
180 | def __resizeColumns(self): | |
181 | """ | |
182 | Private method to resize the columns of the result list. | |
183 | """ | |
184 | self.resultsList.header().resizeSections( | |
185 | QHeaderView.ResizeMode.ResizeToContents | |
186 | ) | |
187 | self.resultsList.header().setStretchLastSection(True) | |
188 | ||
189 | def __populateStatusFilterCombo(self): | |
190 | """ | |
191 | Private method to populate the status filter combo box with allowed selections. | |
192 | """ | |
193 | allowedSelections = set() | |
194 | for row in range(self.resultsList.topLevelItemCount()): | |
195 | allowedSelections.add( | |
196 | self.resultsList.topLevelItem(row).text( | |
197 | IsortFormattingDialog.StatusColumn | |
198 | ) | |
199 | ) | |
200 | ||
201 | self.statusFilterComboBox.addItem(self.__allFilter) | |
202 | self.statusFilterComboBox.addItems(sorted(allowedSelections)) | |
203 | ||
204 | def __finish(self): | |
205 | """ | |
206 | Private method to perform some actions after the run was performed or canceled. | |
207 | """ | |
208 | self.__resort() | |
209 | self.__resizeColumns() | |
210 | ||
211 | self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False) | |
212 | self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True) | |
213 | self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault(True) | |
214 | ||
215 | self.progressBar.setVisible(False) | |
216 | ||
217 | self.__sortImportsButton.setVisible( | |
218 | self.__config["__action__"] is not IsortFormattingAction.Sort | |
219 | and self.__statistics.changeCount > 0 | |
220 | ) | |
221 | ||
222 | self.__updateStatistics() | |
223 | self.__populateStatusFilterCombo() | |
224 | ||
225 | def __updateStatistics(self): | |
226 | """ | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
227 | Private method to update the statistics about the recent sorting run and |
9453 | 228 | make them visible. |
229 | """ | |
230 | self.reformattedLabel.setText( | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
231 | self.tr("Resorted:") |
9453 | 232 | if self.__config["__action__"] is IsortFormattingAction.Sort |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
233 | else self.tr("Would Resort:") |
9453 | 234 | ) |
235 | ||
236 | total = self.progressBar.maximum() | |
237 | ||
238 | self.totalCountLabel.setText("{0:n}".format(total)) | |
239 | self.skippedCountLabel.setText("{0:n}".format(self.__statistics.skippedCount)) | |
240 | self.failuresCountLabel.setText("{0:n}".format(self.__statistics.failureCount)) | |
241 | self.processedCountLabel.setText( | |
242 | "{0:n}".format(self.__statistics.processedCount) | |
243 | ) | |
244 | self.reformattedCountLabel.setText( | |
245 | "{0:n}".format(self.__statistics.changeCount) | |
246 | ) | |
247 | self.unchangedCountLabel.setText("{0:n}".format(self.__statistics.sameCount)) | |
248 | ||
249 | self.statisticsGroup.setVisible(True) | |
250 | ||
251 | @pyqtSlot(QAbstractButton) | |
252 | def on_buttonBox_clicked(self, button): | |
253 | """ | |
254 | Private slot to handle button presses of the dialog buttons. | |
255 | ||
256 | @param button reference to the pressed button | |
257 | @type QAbstractButton | |
258 | """ | |
259 | if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel): | |
260 | self.__cancelled = True | |
261 | elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close): | |
262 | self.accept() | |
263 | elif button is self.__sortImportsButton: | |
264 | self.__sortImportsButtonClicked() | |
265 | ||
266 | @pyqtSlot() | |
267 | def __sortImportsButtonClicked(self): | |
268 | """ | |
269 | Private slot handling the selection of the 'Sort Imports' button. | |
270 | """ | |
9460
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
271 | files = [] |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
272 | for row in range(self.resultsList.topLevelItemCount()): |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
273 | itm = self.resultsList.topLevelItem(row) |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
274 | if itm.data(0, IsortFormattingDialog.StatusRole) == "changed": |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
275 | files.append(itm.data(0, IsortFormattingDialog.FileNameRole)) |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
276 | if files: |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
277 | self.__filesList = files |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
278 | |
9453 | 279 | self.__config["__action__"] = IsortFormattingAction.Sort |
280 | self.__performAction() | |
281 | ||
282 | @pyqtSlot(QTreeWidgetItem, int) | |
283 | def on_resultsList_itemDoubleClicked(self, item, column): | |
284 | """ | |
285 | Private slot handling a double click of a result item. | |
286 | ||
287 | @param item reference to the double clicked item | |
288 | @type QTreeWidgetItem | |
289 | @param column column number that was double clicked | |
290 | @type int | |
291 | """ | |
292 | dataType = item.data(0, IsortFormattingDialog.DataTypeRole) | |
293 | if dataType == "error": | |
294 | EricMessageBox.critical( | |
295 | self, | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
296 | self.tr("Imports Sorting Failure"), |
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
297 | self.tr( |
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
298 | "<p>Imports sorting failed due to this error.</p><p>{0}</p>" |
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
299 | ).format(item.data(0, IsortFormattingDialog.DataRole)), |
9453 | 300 | ) |
301 | elif dataType == "diff": | |
302 | if self.__diffDialog is None: | |
303 | self.__diffDialog = FormattingDiffWidget() | |
304 | self.__diffDialog.showDiff(item.data(0, IsortFormattingDialog.DataRole)) | |
305 | ||
306 | @pyqtSlot(str) | |
307 | def on_statusFilterComboBox_currentTextChanged(self, status): | |
308 | """ | |
309 | Private slot handling the selection of a status for items to be shown. | |
310 | ||
311 | @param status selected status | |
312 | @type str | |
313 | """ | |
314 | for row in range(self.resultsList.topLevelItemCount()): | |
315 | itm = self.resultsList.topLevelItem(row) | |
316 | itm.setHidden( | |
317 | status != self.__allFilter | |
318 | and itm.text(IsortFormattingDialog.StatusColumn) != status | |
319 | ) | |
320 | ||
321 | def closeEvent(self, evt): | |
322 | """ | |
323 | Protected slot implementing a close event handler. | |
324 | ||
325 | @param evt reference to the close event | |
326 | @type QCloseEvent | |
327 | """ | |
328 | if self.__diffDialog is not None: | |
329 | self.__diffDialog.close() | |
330 | evt.accept() | |
331 | ||
332 | def __handleIsortResult(self, filename, status, data=""): | |
333 | """ | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
334 | Private method to handle an isort sorting result. |
9453 | 335 | |
336 | @param filename name of the processed file | |
337 | @type str | |
338 | @param status status of the performed action (one of 'changed', 'failed', | |
339 | 'skipped' or 'unchanged') | |
340 | @type str | |
341 | @param data action data (error message or unified diff) (defaults to "") | |
342 | @type str (optional) | |
343 | """ | |
344 | isError = False | |
345 | ||
346 | if status == "changed": | |
347 | statusMsg = ( | |
348 | self.tr("would resort") | |
349 | if self.__config["__action__"] | |
350 | in (IsortFormattingAction.Check, IsortFormattingAction.Diff) | |
351 | else self.tr("resorted") | |
352 | ) | |
353 | self.__statistics.changeCount += 1 | |
354 | ||
355 | elif status == "unchanged": | |
356 | statusMsg = self.tr("unchanged") | |
357 | self.__statistics.sameCount += 1 | |
358 | ||
359 | elif status == "skipped": | |
360 | statusMsg = self.tr("skipped") | |
361 | self.__statistics.skippedCount += 1 | |
362 | ||
363 | elif status == "failed": | |
364 | statusMsg = self.tr("failed") | |
365 | self.__statistics.failureCount += 1 | |
366 | isError = True | |
367 | ||
368 | elif status == "unsupported": | |
369 | statusMsg = self.tr("error") | |
370 | data = self.tr("Unsupported 'isort' action ({0}) given.").format( | |
371 | self.__config["__action__"] | |
372 | ) | |
373 | self.__statistics.failureCount += 1 | |
374 | isError = True | |
375 | ||
376 | else: | |
377 | statusMsg = self.tr("invalid status ({0})").format(status) | |
378 | self.__statistics.failureCount += 1 | |
379 | isError = True | |
380 | ||
381 | if status != "skipped": | |
382 | self.__statistics.processedCount += 1 | |
383 | ||
9460
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
384 | itm = QTreeWidgetItem( |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
385 | self.resultsList, |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
386 | [ |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
387 | statusMsg, |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
388 | self.__project.getRelativePath(filename) |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
389 | if self.__project |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
390 | else filename, |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
391 | ], |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
392 | ) |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
393 | itm.setData(0, IsortFormattingDialog.StatusRole, status) |
0d1b5d0fd815
Modified the Black and Isort dialogs such, that only files that need a change are processed after a diff or check run by pressing the relevant button.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9455
diff
changeset
|
394 | itm.setData(0, IsortFormattingDialog.FileNameRole, filename) |
9453 | 395 | if data: |
396 | itm.setData( | |
397 | 0, IsortFormattingDialog.DataTypeRole, "error" if isError else "diff" | |
398 | ) | |
399 | itm.setData(0, IsortFormattingDialog.DataRole, data) | |
400 | ||
401 | self.progressBar.setValue(self.progressBar.value() + 1) | |
402 | ||
403 | QCoreApplication.processEvents() | |
404 | ||
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
405 | def __sortManyFiles(self, files): |
9453 | 406 | """ |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
407 | Private method to sort imports of the list of files according the configuration |
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
408 | using multiple processes in parallel. |
9453 | 409 | |
410 | @param files list of files to be processed | |
411 | @type list of str | |
412 | """ | |
413 | maxProcesses = Preferences.getUI("BackgroundServiceProcesses") | |
414 | if maxProcesses == 0: | |
415 | # determine based on CPU count | |
416 | try: | |
417 | NumberOfProcesses = multiprocessing.cpu_count() | |
418 | if NumberOfProcesses >= 1: | |
419 | NumberOfProcesses -= 1 | |
420 | except NotImplementedError: | |
421 | NumberOfProcesses = 1 | |
422 | else: | |
423 | NumberOfProcesses = maxProcesses | |
424 | ||
425 | # Create queues | |
426 | taskQueue = multiprocessing.Queue() | |
427 | doneQueue = multiprocessing.Queue() | |
428 | ||
429 | # Submit tasks (initially two times the number of processes) | |
430 | tasks = len(files) | |
431 | initialTasks = min(2 * NumberOfProcesses, tasks) | |
432 | for _ in range(initialTasks): | |
433 | file = files.pop(0) | |
434 | taskQueue.put((file, self.__config["__action__"])) | |
435 | ||
436 | # Start worker processes | |
437 | workers = [ | |
438 | multiprocessing.Process( | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
439 | target=self.sortingWorkerTask, |
9453 | 440 | args=(taskQueue, doneQueue, self.__isortConfig), |
441 | ) | |
442 | for _ in range(NumberOfProcesses) | |
443 | ] | |
444 | for worker in workers: | |
445 | worker.start() | |
446 | ||
447 | # Get the results from the worker tasks | |
448 | for _ in range(tasks): | |
449 | result = doneQueue.get() | |
450 | self.__handleIsortResult(result.filename, result.status, data=result.data) | |
451 | ||
452 | if self.__cancelled: | |
453 | break | |
454 | ||
455 | if files: | |
456 | file = files.pop(0) | |
457 | taskQueue.put((file, self.__config["__action__"])) | |
458 | ||
459 | # Tell child processes to stop | |
460 | for _ in range(NumberOfProcesses): | |
461 | taskQueue.put("STOP") | |
462 | ||
463 | for worker in workers: | |
464 | worker.join() | |
465 | worker.close() | |
466 | ||
467 | taskQueue.close() | |
468 | doneQueue.close() | |
469 | ||
470 | self.__finish() | |
471 | ||
472 | @staticmethod | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
473 | def sortingWorkerTask(inputQueue, outputQueue, isortConfig): |
9453 | 474 | """ |
475 | Static method acting as the parallel worker for the formatting task. | |
476 | ||
477 | @param inputQueue input queue | |
478 | @type multiprocessing.Queue | |
479 | @param outputQueue output queue | |
480 | @type multiprocessing.Queue | |
481 | @param isortConfig config object for isort | |
482 | @type isort.Config | |
483 | """ | |
484 | for file, action in iter(inputQueue.get, "STOP"): | |
485 | if action == IsortFormattingAction.Diff: | |
486 | result = IsortFormattingDialog.__isortCheckFile( | |
487 | file, | |
488 | isortConfig, | |
489 | withDiff=True, | |
490 | ) | |
491 | ||
492 | elif action == IsortFormattingAction.Sort: | |
493 | result = IsortFormattingDialog.__isortSortFile( | |
494 | file, | |
495 | isortConfig, | |
496 | ) | |
497 | ||
498 | else: | |
499 | result = IsortResult( | |
500 | status="unsupported", | |
501 | filename=file, | |
502 | ) | |
503 | ||
504 | outputQueue.put(result) | |
505 | ||
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
506 | def __sortOneFile(self, file): |
9453 | 507 | """ |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
508 | Private method to sort the imports of the list of files according the |
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
509 | configuration. |
9453 | 510 | |
511 | @param file name of the file to be processed | |
512 | @type str | |
513 | """ | |
514 | if self.__config["__action__"] == IsortFormattingAction.Diff: | |
515 | result = IsortFormattingDialog.__isortCheckFile( | |
516 | file, | |
517 | self.__isortConfig, | |
518 | withDiff=True, | |
519 | ) | |
520 | ||
521 | elif self.__config["__action__"] == IsortFormattingAction.Sort: | |
522 | result = IsortFormattingDialog.__isortSortFile( | |
523 | file, | |
524 | self.__isortConfig, | |
525 | ) | |
526 | ||
527 | else: | |
528 | result = IsortResult( | |
529 | status="unsupported", | |
530 | filename=file, | |
531 | ) | |
532 | ||
533 | self.__handleIsortResult(result.filename, result.status, data=result.data) | |
534 | ||
535 | self.__finish() | |
536 | ||
537 | @staticmethod | |
538 | def __isortCheckFile(filename, isortConfig, withDiff=True): | |
539 | """ | |
540 | Static method to check, if a file's import statements need to be changed. | |
541 | ||
542 | @param filename name of the file to be processed | |
543 | @type str | |
544 | @param isortConfig config object for isort | |
545 | @type isort.Config | |
546 | @param withDiff flag indicating to return a unified diff, if the file needs to | |
547 | be changed (defaults to True) | |
548 | @type bool (optional) | |
549 | @return result object | |
550 | @rtype IsortResult | |
551 | """ | |
552 | diffIO = io.StringIO() if withDiff else False | |
553 | with suppressStderr(): | |
554 | ok = check_file(filename, show_diff=diffIO, config=isortConfig) | |
555 | if withDiff: | |
556 | data = "" if ok else diffIO.getvalue() | |
557 | diffIO.close() | |
558 | else: | |
559 | data = "" | |
560 | ||
561 | status = "unchanged" if ok else "changed" | |
562 | ||
563 | return IsortResult(status=status, filename=filename, data=data) | |
564 | ||
565 | @staticmethod | |
566 | def __isortSortFile(filename, isortConfig): | |
567 | """ | |
568 | Static method to sort the import statements of a file. | |
569 | ||
570 | @param filename name of the file to be processed | |
571 | @type str | |
572 | @param isortConfig config object for isort | |
573 | @type isort.Config | |
574 | @return result object | |
575 | @rtype IsortResult | |
576 | """ | |
577 | with suppressStderr(): | |
578 | ok = sort_file( | |
579 | filename, | |
580 | config=isortConfig, | |
581 | ask_to_apply=False, | |
582 | write_to_stdout=False, | |
583 | show_diff=False, | |
584 | ) | |
585 | ||
586 | status = "changed" if ok else "unchanged" | |
587 | ||
588 | return IsortResult(status=status, filename=filename) | |
589 | ||
590 | ||
591 | @dataclass | |
592 | class IsortStatistics: | |
593 | """ | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
594 | Class containing the isort statistic data. |
9453 | 595 | """ |
596 | ||
597 | skippedCount: int = 0 | |
598 | changeCount: int = 0 | |
599 | sameCount: int = 0 | |
600 | failureCount: int = 0 | |
601 | processedCount: int = 0 | |
602 | ||
603 | ||
604 | @dataclass | |
605 | class IsortResult: | |
606 | """ | |
9455
5f138ee215a5
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9453
diff
changeset
|
607 | Class containing the isort result data. |
9453 | 608 | """ |
609 | ||
610 | status: str = "" | |
611 | filename: str = "" | |
612 | data: str = "" |