From d5eeab4a179b42e1f7ee521448e38da83e41e250 Mon Sep 17 00:00:00 2001 From: Andrew Senetar Date: Wed, 11 May 2022 00:50:34 -0500 Subject: [PATCH] Additional type hints in hscommon --- hscommon/gui/base.py | 8 +-- hscommon/gui/column.py | 63 ++++++++++--------- hscommon/gui/progress_window.py | 21 ++++--- hscommon/gui/table.py | 101 ++++++++++++++++-------------- hscommon/jobprogress/job.py | 38 ++++++----- hscommon/jobprogress/performer.py | 13 ++-- 6 files changed, 133 insertions(+), 111 deletions(-) diff --git a/hscommon/gui/base.py b/hscommon/gui/base.py index 4e911a82..6ba0bd35 100644 --- a/hscommon/gui/base.py +++ b/hscommon/gui/base.py @@ -36,11 +36,11 @@ class GUIObject: ``multibind`` flag to ``True`` and the safeguard will be disabled. """ - def __init__(self, multibind=False): + def __init__(self, multibind: bool = False) -> None: self._view = None self._multibind = multibind - def _view_updated(self): + def _view_updated(self) -> None: """(Virtual) Called after :attr:`view` has been set. Doing nothing by default, this method is called after :attr:`view` has been set (it isn't @@ -48,7 +48,7 @@ class GUIObject: (which is often the whole of the initialization code). """ - def has_view(self): + def has_view(self) -> bool: return (self._view is not None) and (not isinstance(self._view, NoopGUI)) @property @@ -67,7 +67,7 @@ class GUIObject: return self._view @view.setter - def view(self, value): + def view(self, value) -> None: if self._view is None and value is None: # Initial view assignment return diff --git a/hscommon/gui/column.py b/hscommon/gui/column.py index dfa75981..942546ac 100644 --- a/hscommon/gui/column.py +++ b/hscommon/gui/column.py @@ -7,8 +7,10 @@ # http://www.gnu.org/licenses/gpl-3.0.html import copy +from typing import Any, List, Tuple, Union from hscommon.gui.base import GUIObject +from hscommon.gui.table import GUITable class Column: @@ -17,7 +19,7 @@ class Column: These attributes are then used to correctly configure the column on the "view" side. """ - def __init__(self, name, display="", visible=True, optional=False): + def __init__(self, name: str, display: str = "", visible: bool = True, optional: bool = False) -> None: #: "programmatical" (not for display) name. Used as a reference in a couple of place, such #: as :meth:`Columns.column_by_name`. self.name = name @@ -52,14 +54,14 @@ class ColumnsView: callbacks. """ - def restore_columns(self): + def restore_columns(self) -> None: """Update all columns according to the model. When this is called, our view has to update the columns title, order and visibility of all columns. """ - def set_column_visible(self, colname, visible): + def set_column_visible(self, colname: str, visible: bool) -> None: """Update visibility of column ``colname``. Called when the user toggles the visibility of a column, we must update the column @@ -73,13 +75,13 @@ class PrefAccessInterface: *Not actually used in the code. For documentation purposes only.* """ - def get_default(self, key, fallback_value): + def get_default(self, key: str, fallback_value: Union[Any, None]) -> Any: """Retrieve the value for ``key`` in the currently running app's preference store. If the key doesn't exist, return ``fallback_value``. """ - def set_default(self, key, value): + def set_default(self, key: str, value: Any) -> None: """Set the value ``value`` for ``key`` in the currently running app's preference store.""" @@ -104,65 +106,65 @@ class Columns(GUIObject): have that same prefix. """ - def __init__(self, table, prefaccess=None, savename=None): + def __init__(self, table: GUITable, prefaccess=None, savename: Union[str, None] = None): GUIObject.__init__(self) self.table = table self.prefaccess = prefaccess self.savename = savename # We use copy here for test isolation. If we don't, changing a column affects all tests. - self.column_list = list(map(copy.copy, table.COLUMNS)) + self.column_list: List[Column] = list(map(copy.copy, table.COLUMNS)) for i, column in enumerate(self.column_list): column.logical_index = i column.ordered_index = i self.coldata = {col.name: col for col in self.column_list} # --- Private - def _get_colname_attr(self, colname, attrname, default): + def _get_colname_attr(self, colname: str, attrname: str, default: Any) -> Any: try: return getattr(self.coldata[colname], attrname) except KeyError: return default - def _set_colname_attr(self, colname, attrname, value): + def _set_colname_attr(self, colname: str, attrname: str, value: Any) -> None: try: col = self.coldata[colname] setattr(col, attrname, value) except KeyError: pass - def _optional_columns(self): + def _optional_columns(self) -> List[Column]: return [c for c in self.column_list if c.optional] # --- Override - def _view_updated(self): + def _view_updated(self) -> None: self.restore_columns() # --- Public - def column_by_index(self, index): + def column_by_index(self, index: int): """Return the :class:`Column` having the :attr:`~Column.logical_index` ``index``.""" return self.column_list[index] - def column_by_name(self, name): + def column_by_name(self, name: str): """Return the :class:`Column` having the :attr:`~Column.name` ``name``.""" return self.coldata[name] - def columns_count(self): + def columns_count(self) -> int: """Returns the number of columns in our set.""" return len(self.column_list) - def column_display(self, colname): + def column_display(self, colname: str) -> str: """Returns display name for column named ``colname``, or ``''`` if there's none.""" return self._get_colname_attr(colname, "display", "") - def column_is_visible(self, colname): + def column_is_visible(self, colname: str) -> bool: """Returns visibility for column named ``colname``, or ``True`` if there's none.""" return self._get_colname_attr(colname, "visible", True) - def column_width(self, colname): + def column_width(self, colname: str) -> int: """Returns width for column named ``colname``, or ``0`` if there's none.""" return self._get_colname_attr(colname, "width", 0) - def columns_to_right(self, colname): + def columns_to_right(self, colname: str) -> List[str]: """Returns the list of all columns to the right of ``colname``. "right" meaning "having a higher :attr:`Column.ordered_index`" in our left-to-right @@ -172,7 +174,7 @@ class Columns(GUIObject): index = column.ordered_index return [col.name for col in self.column_list if (col.visible and col.ordered_index > index)] - def menu_items(self): + def menu_items(self) -> List[Tuple[str, bool]]: """Returns a list of items convenient for quick visibility menu generation. Returns a list of ``(display_name, is_marked)`` items for each optional column in the @@ -184,7 +186,7 @@ class Columns(GUIObject): """ return [(c.display, c.visible) for c in self._optional_columns()] - def move_column(self, colname, index): + def move_column(self, colname: str, index: int) -> None: """Moves column ``colname`` to ``index``. The column will be placed just in front of the column currently having that index, or to the @@ -195,7 +197,7 @@ class Columns(GUIObject): colnames.insert(index, colname) self.set_column_order(colnames) - def reset_to_defaults(self): + def reset_to_defaults(self) -> None: """Reset all columns' width and visibility to their default values.""" self.set_column_order([col.name for col in self.column_list]) for col in self._optional_columns(): @@ -203,11 +205,11 @@ class Columns(GUIObject): col.width = col.default_width self.view.restore_columns() - def resize_column(self, colname, newwidth): + def resize_column(self, colname: str, newwidth: int) -> None: """Set column ``colname``'s width to ``newwidth``.""" self._set_colname_attr(colname, "width", newwidth) - def restore_columns(self): + def restore_columns(self) -> None: """Restore's column persistent attributes from the last :meth:`save_columns`.""" if not (self.prefaccess and self.savename and self.coldata): if (not self.savename) and (self.coldata): @@ -226,7 +228,7 @@ class Columns(GUIObject): col.visible = coldata["visible"] self.view.restore_columns() - def save_columns(self): + def save_columns(self) -> None: """Save column attributes in persistent storage for restoration in :meth:`restore_columns`.""" if not (self.prefaccess and self.savename and self.coldata): return @@ -237,7 +239,8 @@ class Columns(GUIObject): coldata["visible"] = col.visible self.prefaccess.set_default(pref_name, coldata) - def set_column_order(self, colnames): + # TODO annotate colnames + def set_column_order(self, colnames) -> None: """Change the columns order so it matches the order in ``colnames``. :param colnames: A list of column names in the desired order. @@ -247,17 +250,17 @@ class Columns(GUIObject): col = self.coldata[colname] col.ordered_index = i - def set_column_visible(self, colname, visible): + def set_column_visible(self, colname: str, visible: bool) -> None: """Set the visibility of column ``colname``.""" self.table.save_edits() # the table on the GUI side will stop editing when the columns change self._set_colname_attr(colname, "visible", visible) self.view.set_column_visible(colname, visible) - def set_default_width(self, colname, width): + def set_default_width(self, colname: str, width: int) -> None: """Set the default width or column ``colname``.""" self._set_colname_attr(colname, "default_width", width) - def toggle_menu_item(self, index): + def toggle_menu_item(self, index: int) -> bool: """Toggles the visibility of an optional column. You know, that optional column menu you've generated in :meth:`menu_items`? Well, ``index`` @@ -271,11 +274,11 @@ class Columns(GUIObject): # --- Properties @property - def ordered_columns(self): + def ordered_columns(self) -> List[Column]: """List of :class:`Column` in visible order.""" return [col for col in sorted(self.column_list, key=lambda col: col.ordered_index)] @property - def colnames(self): + def colnames(self) -> List[str]: """List of column names in visible order.""" return [col.name for col in self.ordered_columns] diff --git a/hscommon/gui/progress_window.py b/hscommon/gui/progress_window.py index 7fdf6022..7adafb1d 100644 --- a/hscommon/gui/progress_window.py +++ b/hscommon/gui/progress_window.py @@ -4,6 +4,7 @@ # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html +from typing import Callable, Tuple, Union from hscommon.jobprogress.performer import ThreadedJobPerformer from hscommon.gui.base import GUIObject from hscommon.gui.text_field import TextField @@ -20,13 +21,13 @@ class ProgressWindowView: It's also expected to call :meth:`ProgressWindow.cancel` when the cancel button is clicked. """ - def show(self): + def show(self) -> None: """Show the dialog.""" - def close(self): + def close(self) -> None: """Close the dialog.""" - def set_progress(self, progress): + def set_progress(self, progress: int) -> None: """Set the progress of the progress bar to ``progress``. Not all jobs are equally responsive on their job progress report and it is recommended that @@ -60,7 +61,11 @@ class ProgressWindow(GUIObject, ThreadedJobPerformer): called as if the job terminated normally. """ - def __init__(self, finish_func, error_func=None): + def __init__( + self, + finish_func: Callable[[Union[str, None]], None], + error_func: Callable[[Union[str, None], Exception], bool] = None, + ) -> None: # finish_func(jobid) is the function that is called when a job is completed. GUIObject.__init__(self) ThreadedJobPerformer.__init__(self) @@ -71,9 +76,9 @@ class ProgressWindow(GUIObject, ThreadedJobPerformer): #: :class:`.TextField`. It contains the job textual update that the function might yield #: during its course. self.progressdesc_textfield = TextField() - self.jobid = None + self.jobid: Union[str, None] = None - def cancel(self): + def cancel(self) -> None: """Call for a user-initiated job cancellation.""" # The UI is sometimes a bit buggy and calls cancel() on self.view.close(). We just want to # make sure that this doesn't lead us to think that the user acually cancelled the task, so @@ -81,7 +86,7 @@ class ProgressWindow(GUIObject, ThreadedJobPerformer): if self._job_running: self.job_cancelled = True - def pulse(self): + def pulse(self) -> None: """Update progress reports in the GUI. Call this regularly from the GUI main run loop. The values might change before @@ -111,7 +116,7 @@ class ProgressWindow(GUIObject, ThreadedJobPerformer): self.progressdesc_textfield.text = last_desc self.view.set_progress(last_progress) - def run(self, jobid, title, target, args=()): + def run(self, jobid: str, title: str, target: Callable, args: Tuple = ()): """Starts a threaded job. The ``target`` function will be sent, as its first argument, a :class:`.Job` instance which diff --git a/hscommon/gui/table.py b/hscommon/gui/table.py index d4e10083..8be9d2d7 100644 --- a/hscommon/gui/table.py +++ b/hscommon/gui/table.py @@ -8,6 +8,7 @@ from collections.abc import MutableSequence from collections import namedtuple +from typing import Any, List, Tuple, Union from hscommon.gui.base import GUIObject from hscommon.gui.selectable_list import Selectable @@ -27,12 +28,16 @@ class Table(MutableSequence, Selectable): Subclasses :class:`.Selectable`. """ - def __init__(self): - Selectable.__init__(self) - self._rows = [] - self._header = None - self._footer = None + # Should be List[Column], but have circular import... + COLUMNS: List = [] + def __init__(self) -> None: + Selectable.__init__(self) + self._rows: List["Row"] = [] + self._header: Union["Row", None] = None + self._footer: Union["Row", None] = None + + # TODO type hint for key def __delitem__(self, key): self._rows.__delitem__(key) if self._header is not None and ((not self) or (self[0] is not self._header)): @@ -41,16 +46,18 @@ class Table(MutableSequence, Selectable): self._footer = None self._check_selection_range() - def __getitem__(self, key): + # TODO type hint for key + def __getitem__(self, key) -> Any: return self._rows.__getitem__(key) - def __len__(self): + def __len__(self) -> int: return len(self._rows) - def __setitem__(self, key, value): + # TODO type hint for key + def __setitem__(self, key, value: Any) -> None: self._rows.__setitem__(key, value) - def append(self, item): + def append(self, item: "Row") -> None: """Appends ``item`` at the end of the table. If there's a footer, the item is inserted before it. @@ -60,7 +67,7 @@ class Table(MutableSequence, Selectable): else: self._rows.append(item) - def insert(self, index, item): + def insert(self, index: int, item: "Row") -> None: """Inserts ``item`` at ``index`` in the table. If there's a header, will make sure we don't insert before it, and if there's a footer, will @@ -72,7 +79,7 @@ class Table(MutableSequence, Selectable): index = len(self) - 1 self._rows.insert(index, item) - def remove(self, row): + def remove(self, row: "Row") -> None: """Removes ``row`` from table. If ``row`` is a header or footer, that header or footer will be set to ``None``. @@ -84,7 +91,7 @@ class Table(MutableSequence, Selectable): self._rows.remove(row) self._check_selection_range() - def sort_by(self, column_name, desc=False): + def sort_by(self, column_name: str, desc: bool = False) -> None: """Sort table by ``column_name``. Sort key for each row is computed from :meth:`Row.sort_key_for_column`. @@ -105,7 +112,7 @@ class Table(MutableSequence, Selectable): # --- Properties @property - def footer(self): + def footer(self) -> Union["Row", None]: """If set, a row that always stay at the bottom of the table. :class:`Row`. *get/set*. @@ -128,7 +135,7 @@ class Table(MutableSequence, Selectable): return self._footer @footer.setter - def footer(self, value): + def footer(self, value: Union["Row", None]) -> None: if self._footer is not None: self._rows.pop() if value is not None: @@ -136,7 +143,7 @@ class Table(MutableSequence, Selectable): self._footer = value @property - def header(self): + def header(self) -> Union["Row", None]: """If set, a row that always stay at the bottom of the table. See :attr:`footer` for details. @@ -144,7 +151,7 @@ class Table(MutableSequence, Selectable): return self._header @header.setter - def header(self, value): + def header(self, value: Union["Row", None]) -> None: if self._header is not None: self._rows.pop(0) if value is not None: @@ -152,7 +159,7 @@ class Table(MutableSequence, Selectable): self._header = value @property - def row_count(self): + def row_count(self) -> int: """Number or rows in the table (without counting header and footer). *int*. *read-only*. @@ -165,7 +172,7 @@ class Table(MutableSequence, Selectable): return result @property - def rows(self): + def rows(self) -> List["Row"]: """List of rows in the table, excluding header and footer. List of :class:`Row`. *read-only*. @@ -179,7 +186,7 @@ class Table(MutableSequence, Selectable): return self[start:end] @property - def selected_row(self): + def selected_row(self) -> "Row": """Selected row according to :attr:`Selectable.selected_index`. :class:`Row`. *get/set*. @@ -190,14 +197,14 @@ class Table(MutableSequence, Selectable): return self[self.selected_index] if self.selected_index is not None else None @selected_row.setter - def selected_row(self, value): + def selected_row(self, value: int) -> None: try: self.selected_index = self.index(value) except ValueError: pass @property - def selected_rows(self): + def selected_rows(self) -> List["Row"]: """List of selected rows based on :attr:`.selected_indexes`. List of :class:`Row`. *read-only*. @@ -219,20 +226,20 @@ class GUITableView: Whenever the user changes the selection, we expect the view to call :meth:`Table.select`. """ - def refresh(self): + def refresh(self) -> None: """Refreshes the contents of the table widget. Ensures that the contents of the table widget is synced with the model. This includes selection. """ - def start_editing(self): + def start_editing(self) -> None: """Start editing the currently selected row. Begin whatever inline editing support that the view supports. """ - def stop_editing(self): + def stop_editing(self) -> None: """Stop editing if there's an inline editing in effect. There's no "aborting" implied in this call, so it's appropriate to send whatever the user @@ -260,33 +267,33 @@ class GUITable(Table, GUIObject): :class:`GUITableView`. """ - def __init__(self): + def __init__(self) -> None: GUIObject.__init__(self) Table.__init__(self) #: The row being currently edited by the user. ``None`` if no edit is taking place. - self.edited = None - self._sort_descriptor = None + self.edited: Union["Row", None] = None + self._sort_descriptor: Union[SortDescriptor, None] = None # --- Virtual - def _do_add(self): + def _do_add(self) -> Tuple["Row", int]: """(Virtual) Creates a new row, adds it in the table. Returns ``(row, insert_index)``. """ raise NotImplementedError() - def _do_delete(self): + def _do_delete(self) -> None: """(Virtual) Delete the selected rows.""" pass - def _fill(self): + def _fill(self) -> None: """(Virtual/Required) Fills the table with all the rows that this table is supposed to have. Called by :meth:`refresh`. Does nothing by default. """ pass - def _is_edited_new(self): + def _is_edited_new(self) -> bool: """(Virtual) Returns whether the currently edited row should be considered "new". This is used in :meth:`cancel_edits` to know whether the cancellation of the edit means a @@ -315,7 +322,7 @@ class GUITable(Table, GUIObject): self.select([len(self) - 1]) # --- Public - def add(self): + def add(self) -> None: """Add a new row in edit mode. Requires :meth:`do_add` to be implemented. The newly added row will be selected and in edit @@ -334,7 +341,7 @@ class GUITable(Table, GUIObject): self.edited = row self.view.start_editing() - def can_edit_cell(self, column_name, row_index): + def can_edit_cell(self, column_name: str, row_index: int) -> bool: """Returns whether the cell at ``row_index`` and ``column_name`` can be edited. A row is, by default, editable as soon as it has an attr with the same name as `column`. @@ -346,7 +353,7 @@ class GUITable(Table, GUIObject): row = self[row_index] return row.can_edit_cell(column_name) - def cancel_edits(self): + def cancel_edits(self) -> None: """Cancels the current edit operation. If there's an :attr:`edited` row, it will be re-initialized (with :meth:`Row.load`). @@ -364,7 +371,7 @@ class GUITable(Table, GUIObject): self.edited = None self.view.refresh() - def delete(self): + def delete(self) -> None: """Delete the currently selected rows. Requires :meth:`_do_delete` for this to have any effect on the model. Cancels editing if @@ -377,7 +384,7 @@ class GUITable(Table, GUIObject): if self: self._do_delete() - def refresh(self, refresh_view=True): + def refresh(self, refresh_view: bool = True) -> None: """Empty the table and re-create its rows. :meth:`_fill` is called after we emptied the table to create our rows. Previous sort order @@ -399,7 +406,7 @@ class GUITable(Table, GUIObject): if refresh_view: self.view.refresh() - def save_edits(self): + def save_edits(self) -> None: """Commit user edits to the model. This is done by calling :meth:`Row.save`. @@ -410,7 +417,7 @@ class GUITable(Table, GUIObject): self.edited = None row.save() - def sort_by(self, column_name, desc=False): + def sort_by(self, column_name: str, desc: bool = False) -> None: """Sort table by ``column_name``. Overrides :meth:`Table.sort_by`. After having performed sorting, calls @@ -450,18 +457,18 @@ class Row: Of course, this is only default behavior. This can be overriden. """ - def __init__(self, table): + def __init__(self, table: GUITable) -> None: super().__init__() self.table = table - def _edit(self): + def _edit(self) -> None: if self.table.edited is self: return assert self.table.edited is None self.table.edited = self # --- Virtual - def can_edit(self): + def can_edit(self) -> bool: """(Virtual) Whether the whole row can be edited. By default, always returns ``True``. This is for the *whole* row. For individual cells, it's @@ -469,7 +476,7 @@ class Row: """ return True - def load(self): + def load(self) -> None: """(Virtual/Required) Loads up values from the model to be presented in the table. Usually, our model instances contain values that are not quite ready for display. If you @@ -478,7 +485,7 @@ class Row: """ raise NotImplementedError() - def save(self): + def save(self) -> None: """(Virtual/Required) Saves user edits into your model. If your table is editable, this is called when the user commits his changes. Usually, these @@ -487,7 +494,7 @@ class Row: """ raise NotImplementedError() - def sort_key_for_column(self, column_name): + def sort_key_for_column(self, column_name: str) -> Any: """(Virtual) Return the value that is to be used to sort by column ``column_name``. By default, looks for an attribute with the same name as ``column_name``, but with an @@ -500,7 +507,7 @@ class Row: return getattr(self, column_name) # --- Public - def can_edit_cell(self, column_name): + def can_edit_cell(self, column_name: str) -> bool: """Returns whether cell for column ``column_name`` can be edited. By the default, the check is done in many steps: @@ -530,7 +537,7 @@ class Row: return False return bool(getattr(prop, "fset", None)) - def get_cell_value(self, attrname): + def get_cell_value(self, attrname: str) -> Any: """Get cell value for ``attrname``. By default, does a simple ``getattr()``, but it is used to allow subclasses to have @@ -540,7 +547,7 @@ class Row: attrname = "from_" return getattr(self, attrname) - def set_cell_value(self, attrname, value): + def set_cell_value(self, attrname: str, value: Any) -> None: """Set cell value to ``value`` for ``attrname``. By default, does a simple ``setattr()``, but it is used to allow subclasses to have diff --git a/hscommon/jobprogress/job.py b/hscommon/jobprogress/job.py index bb94a225..5f52d2d9 100644 --- a/hscommon/jobprogress/job.py +++ b/hscommon/jobprogress/job.py @@ -7,6 +7,9 @@ # http://www.gnu.org/licenses/gpl-3.0.html +from typing import Any, Callable, Generator, Iterator, List, Union + + class JobCancelled(Exception): "The user has cancelled the job" @@ -36,7 +39,7 @@ class Job: """ # ---Magic functions - def __init__(self, job_proportions, callback): + def __init__(self, job_proportions: Union[List[int], int], callback: Callable) -> None: """Initialize the Job with 'jobcount' jobs. Start every job with start_job(). Every time the job progress is updated, 'callback' is called 'callback' takes a 'progress' int param, and a optional 'desc' @@ -55,12 +58,12 @@ class Job: self._currmax = 1 # ---Private - def _subjob_callback(self, progress, desc=""): + def _subjob_callback(self, progress: int, desc: str = "") -> bool: """This is the callback passed to children jobs.""" self.set_progress(progress, desc) return True # if JobCancelled has to be raised, it will be at the highest level - def _do_update(self, desc): + def _do_update(self, desc: str) -> None: """Calls the callback function with a % progress as a parameter. The parameter is a int in the 0-100 range. @@ -78,13 +81,16 @@ class Job: raise JobCancelled() # ---Public - def add_progress(self, progress=1, desc=""): + def add_progress(self, progress: int = 1, desc: str = "") -> None: self.set_progress(self._progress + progress, desc) - def check_if_cancelled(self): + def check_if_cancelled(self) -> None: self._do_update("") - def iter_with_progress(self, iterable, desc_format=None, every=1, count=None): + # TODO type hint iterable + def iter_with_progress( + self, iterable, desc_format: Union[str, None] = None, every: int = 1, count: Union[int, None] = None + ) -> Generator[Any, None, None]: """Iterate through ``iterable`` while automatically adding progress. WARNING: We need our iterable's length. If ``iterable`` is not a sequence (that is, @@ -107,7 +113,7 @@ class Job: desc = desc_format % (count, count) self.set_progress(100, desc) - def start_job(self, max_progress=100, desc=""): + def start_job(self, max_progress: int = 100, desc: str = "") -> None: """Begin work on the next job. You must not call start_job more than 'jobcount' (in __init__) times. 'max' is the job units you are to perform. @@ -122,7 +128,7 @@ class Job: self._currmax = max(1, max_progress) self._do_update(desc) - def start_subjob(self, job_proportions, desc=""): + def start_subjob(self, job_proportions: Union[List[int], int], desc: str = "") -> "Job": """Starts a sub job. Use this when you want to split a job into multiple smaller jobs. Pretty handy when starting a process where you know how many subjobs you will have, but don't know the work unit count @@ -132,7 +138,7 @@ class Job: self.start_job(100, desc) return Job(job_proportions, self._subjob_callback) - def set_progress(self, progress, desc=""): + def set_progress(self, progress: int, desc: str = "") -> None: """Sets the progress of the current job to 'progress', and call the callback """ @@ -143,29 +149,29 @@ class Job: class NullJob: - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: # Null job does nothing pass - def add_progress(self, *args, **kwargs): + def add_progress(self, *args, **kwargs) -> None: # Null job does nothing pass - def check_if_cancelled(self): + def check_if_cancelled(self) -> None: # Null job does nothing pass - def iter_with_progress(self, sequence, *args, **kwargs): + def iter_with_progress(self, sequence, *args, **kwargs) -> Iterator: return iter(sequence) - def start_job(self, *args, **kwargs): + def start_job(self, *args, **kwargs) -> None: # Null job does nothing pass - def start_subjob(self, *args, **kwargs): + def start_subjob(self, *args, **kwargs) -> "NullJob": return NullJob() - def set_progress(self, *args, **kwargs): + def set_progress(self, *args, **kwargs) -> None: # Null job does nothing pass diff --git a/hscommon/jobprogress/performer.py b/hscommon/jobprogress/performer.py index bdb1473a..8e64e540 100644 --- a/hscommon/jobprogress/performer.py +++ b/hscommon/jobprogress/performer.py @@ -8,6 +8,7 @@ from threading import Thread import sys +from typing import Callable, Tuple, Union from hscommon.jobprogress.job import Job, JobInProgressError, JobCancelled @@ -28,15 +29,15 @@ class ThreadedJobPerformer: last_error = None # --- Protected - def create_job(self): + def create_job(self) -> Job: if self._job_running: raise JobInProgressError() - self.last_progress = -1 + self.last_progress: Union[int, None] = -1 self.last_desc = "" self.job_cancelled = False return Job(1, self._update_progress) - def _async_run(self, *args): + def _async_run(self, *args) -> None: target = args[0] args = tuple(args[1:]) self._job_running = True @@ -52,7 +53,7 @@ class ThreadedJobPerformer: self._job_running = False self.last_progress = None - def reraise_if_error(self): + def reraise_if_error(self) -> None: """Reraises the error that happened in the thread if any. Call this after the caller of run_threaded detected that self._job_running returned to False @@ -60,13 +61,13 @@ class ThreadedJobPerformer: if self.last_error is not None: raise self.last_error.with_traceback(self.last_traceback) - def _update_progress(self, newprogress, newdesc=""): + def _update_progress(self, newprogress: int, newdesc: str = "") -> bool: self.last_progress = newprogress if newdesc: self.last_desc = newdesc return not self.job_cancelled - def run_threaded(self, target, args=()): + def run_threaded(self, target: Callable, args: Tuple = ()) -> None: if self._job_running: raise JobInProgressError() args = (target,) + args