diff --git a/.github/workflows/python_actions.yml b/.github/workflows/python_actions.yml index e81d2699..2b786d7d 100644 --- a/.github/workflows/python_actions.yml +++ b/.github/workflows/python_actions.yml @@ -64,6 +64,7 @@ jobs: uses: ./support/actions/pylint with: package: ${{ env.ROOT_PKG }} + exitcheck: 31 # Action fails on any message language: en_GB - name: Lint with mypy run: mypy $ROOT_PKG diff --git a/.pylint_dict.txt b/.pylint_dict.txt index 63a10aff..54a82ad1 100644 --- a/.pylint_dict.txt +++ b/.pylint_dict.txt @@ -18,14 +18,24 @@ Zenodo LCM # Python types -RawConfigParser AbstractBase LogCapture +RawConfigParser ZipFile # Python packages -# numpy -# mypy +numpy +mypy + +# Our "special" words +aplx +config +configs +metaclasses + +# Python bits +iter +UnusedVariable # Misc haha diff --git a/.pylintrc b/.pylintrc index afc016ea..32418d46 100644 --- a/.pylintrc +++ b/.pylintrc @@ -68,7 +68,12 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=R,C,unsubscriptable-object +disable=consider-using-dict-items, missing-module-docstring, unsubscriptable-object + +# consider-using-dict-items "for x in foo:" is fine! + +# missing-module-docstring expects a comment at import level which we don't do + # False positives for unsubscriptable-object. Mypy better at this class of issue # See https://github.com/pylint-dev/pylint/issues/1498 @@ -76,7 +81,7 @@ disable=R,C,unsubscriptable-object # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member,C0402,C0403 +enable= [REPORTS] diff --git a/spinn_utilities/abstract_base.py b/spinn_utilities/abstract_base.py index 3d929b9d..7d6cad79 100644 --- a/spinn_utilities/abstract_base.py +++ b/spinn_utilities/abstract_base.py @@ -40,6 +40,7 @@ def my_abstract_method(self, ...): return funcobj +# pylint: disable=invalid-name class abstractproperty(property): """ A decorator indicating abstract properties. @@ -110,9 +111,9 @@ def my_abstract_method(self, ...): ... """ - def __new__(cls, name, bases, namespace, **kwargs): + def __new__(mcs, name, bases, namespace, **kwargs): # Actually make the class - abs_cls = super().__new__(cls, name, bases, namespace, **kwargs) + abs_cls = super().__new__(mcs, name, bases, namespace, **kwargs) # Get set of abstract methods from namespace abstracts = set(nm for nm, val in namespace.items() diff --git a/spinn_utilities/abstract_context_manager.py b/spinn_utilities/abstract_context_manager.py index 38254c08..413492ac 100644 --- a/spinn_utilities/abstract_context_manager.py +++ b/spinn_utilities/abstract_context_manager.py @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .abstract_base import AbstractBase, abstractmethod from types import TracebackType from typing import Optional, Type from typing_extensions import Literal, Self +from .abstract_base import AbstractBase, abstractmethod class AbstractContextManager(object, metaclass=AbstractBase): diff --git a/spinn_utilities/bytestring_utils.py b/spinn_utilities/bytestring_utils.py index 07d3883a..e48334c1 100644 --- a/spinn_utilities/bytestring_utils.py +++ b/spinn_utilities/bytestring_utils.py @@ -46,4 +46,5 @@ def as_hex(byte_string: bytes, start: Optional[int] = None, :return: Comma-separated hex values :rtype: str """ + # pylint: disable=consider-using-f-string return ','.join('%02x' % i for i in iter(byte_string[start:end])) diff --git a/spinn_utilities/citation/citation_aggregator.py b/spinn_utilities/citation/citation_aggregator.py index 7946d1a6..6a289acf 100644 --- a/spinn_utilities/citation/citation_aggregator.py +++ b/spinn_utilities/citation/citation_aggregator.py @@ -41,6 +41,8 @@ CITATION_DOI_TYPE = 'identifier' +# pylint: skip-file + class CitationAggregator(object): """ diff --git a/spinn_utilities/citation/citation_updater_and_doi_generator.py b/spinn_utilities/citation/citation_updater_and_doi_generator.py index a9fcba26..fee43ca5 100644 --- a/spinn_utilities/citation/citation_updater_and_doi_generator.py +++ b/spinn_utilities/citation/citation_updater_and_doi_generator.py @@ -44,6 +44,8 @@ AUTHOR_ORCID = "orcid" IDENTIFIER = 'identifier' +# pylint: skip-file + class _ZenodoException(Exception): """ diff --git a/spinn_utilities/conf_loader.py b/spinn_utilities/conf_loader.py index 38d88c6c..94328016 100644 --- a/spinn_utilities/conf_loader.py +++ b/spinn_utilities/conf_loader.py @@ -12,12 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -import appdirs + from configparser import NoOptionError import logging import os from typing import Callable, Dict, List, Sequence, Tuple, Union + +import appdirs from typing_extensions import TypeAlias + from spinn_utilities import log from spinn_utilities.configs import ( CamelCaseConfigParser, ConfigTemplateException, @@ -26,7 +29,7 @@ _SectionParser: TypeAlias = Callable[[CamelCaseConfigParser], None] -def install_cfg_and_IOError( +def install_cfg_and_error( filename: str, defaults: List[str], config_locations: List[str]) -> NoConfigFoundException: """ @@ -232,7 +235,7 @@ def load_config( config_locations = _config_locations(filename) if not any(os.path.isfile(f) for f in config_locations): if defaults: - raise install_cfg_and_IOError( + raise install_cfg_and_error( filename, defaults, config_locations) else: logger.error("No default cfg files provided") diff --git a/spinn_utilities/config_holder.py b/spinn_utilities/config_holder.py index edb5b8c5..cc6aad90 100644 --- a/spinn_utilities/config_holder.py +++ b/spinn_utilities/config_holder.py @@ -76,7 +76,7 @@ def _pre_load_config() -> CamelCaseConfigParser: :raises ConfigException: Raise if called before setup """ - # If you getthis error during a unittest then unittest_step was not called + # If you get this error during a unit test, unittest_step was not called if not __unittest_mode: raise ConfigException( "Accessing config values before setup is not supported") diff --git a/spinn_utilities/config_setup.py b/spinn_utilities/config_setup.py index c79ac304..40db30dd 100644 --- a/spinn_utilities/config_setup.py +++ b/spinn_utilities/config_setup.py @@ -36,4 +36,7 @@ def unittest_setup() -> None: def add_spinn_utilities_cfg() -> None: + """ + Loads the default config values for spinn_utilities + """ add_default_cfg(os.path.join(os.path.dirname(__file__), BASE_CONFIG_FILE)) diff --git a/spinn_utilities/configs/camel_case_config_parser.py b/spinn_utilities/configs/camel_case_config_parser.py index 7572e86a..7b632d87 100644 --- a/spinn_utilities/configs/camel_case_config_parser.py +++ b/spinn_utilities/configs/camel_case_config_parser.py @@ -22,6 +22,9 @@ class CamelCaseConfigParser(configparser.RawConfigParser): + """ + Extends the Parser to allow for differences in case and underscores + """ __slots__ = ["_read_files"] def optionxform(self, optionstr: str) -> str: diff --git a/spinn_utilities/data/reset_status.py b/spinn_utilities/data/reset_status.py index 9aec41fc..35a6f942 100644 --- a/spinn_utilities/data/reset_status.py +++ b/spinn_utilities/data/reset_status.py @@ -21,8 +21,8 @@ class ResetStatus(Enum): This class is design to used internally by UtilsDataView. """ - NOT_SETUP = (0) - SETUP = (1) - HAS_RUN = (2) - SOFT_RESET = (3) - HARD_RESET = (4) + NOT_SETUP = 0 + SETUP = 1 + HAS_RUN = 2 + SOFT_RESET = 3 + HARD_RESET = 4 diff --git a/spinn_utilities/data/run_status.py b/spinn_utilities/data/run_status.py index ea94c74a..37a83d6f 100644 --- a/spinn_utilities/data/run_status.py +++ b/spinn_utilities/data/run_status.py @@ -27,9 +27,9 @@ class RunStatus(Enum): This class is design to used internally by :py:class:`UtilsDataView`. """ - NOT_SETUP = (0) - NOT_RUNNING = (12) - IN_RUN = (2) - STOP_REQUESTED = (3) - STOPPING = (4) - SHUTDOWN = (5) + NOT_SETUP = 0 + NOT_RUNNING = 1 + IN_RUN = 2 + STOP_REQUESTED = 3 + STOPPING = 4 + SHUTDOWN = 5 diff --git a/spinn_utilities/data/utils_data_view.py b/spinn_utilities/data/utils_data_view.py index 5131333e..c0ff87f1 100644 --- a/spinn_utilities/data/utils_data_view.py +++ b/spinn_utilities/data/utils_data_view.py @@ -16,14 +16,14 @@ from typing import List, Optional from unittest import SkipTest -from .data_status import DataStatus -from .reset_status import ResetStatus -from .run_status import RunStatus from spinn_utilities.exceptions import ( SpiNNUtilsException, SimulatorNotSetupException, SimulatorRunningException, SimulatorShutdownException, UnexpectedStateChange) from spinn_utilities.executable_finder import ExecutableFinder +from .data_status import DataStatus +from .reset_status import ResetStatus +from .run_status import RunStatus # pylint: disable=protected-access diff --git a/spinn_utilities/data/utils_data_writer.py b/spinn_utilities/data/utils_data_writer.py index ee344af3..0966cd6a 100644 --- a/spinn_utilities/data/utils_data_writer.py +++ b/spinn_utilities/data/utils_data_writer.py @@ -18,12 +18,12 @@ from spinn_utilities.exceptions import ( IllegalWriterException, InvalidDirectory, SimulatorNotRunException, UnexpectedStateChange) +from spinn_utilities.executable_finder import ExecutableFinder from spinn_utilities.log import FormatAdapter from .data_status import DataStatus from .reset_status import ResetStatus from .run_status import RunStatus from .utils_data_view import UtilsDataView, _UtilsDataModel -from spinn_utilities.executable_finder import ExecutableFinder logger = FormatAdapter(logging.getLogger(__file__)) # pylint: disable=protected-access diff --git a/spinn_utilities/executable_finder.py b/spinn_utilities/executable_finder.py index c65f5474..76d30c8b 100644 --- a/spinn_utilities/executable_finder.py +++ b/spinn_utilities/executable_finder.py @@ -130,6 +130,11 @@ def get_executable_paths(self, executable_names: str) -> List[str]: return results def check_logs(self) -> None: + """ + Compares the aplx files used against the ones available for use + + Reports the aplx files never used. + """ if not self._paths_log: print("environ GLOBAL_REPORTS not set!") return @@ -167,6 +172,9 @@ def check_logs(self) -> None: print(binary) def clear_logs(self) -> None: + """ + Deletes log files from previous runs + """ if not self._paths_log: print("environ GLOBAL_REPORTS not set!") return diff --git a/spinn_utilities/log.py b/spinn_utilities/log.py index 9d76ac7e..f318b616 100644 --- a/spinn_utilities/log.py +++ b/spinn_utilities/log.py @@ -59,7 +59,7 @@ class ConfiguredFormatter(logging.Formatter): """ Defines the logging format for the SpiNNaker host software. """ - # Precompile this RE; it gets used quite a few times + # Pre-compile this RE; it gets used quite a few times __last_component = re.compile(r'\.[^.]+$') def __init__(self, conf): @@ -197,6 +197,10 @@ def set_kill_level(cls, level: Optional[int] = None): @classmethod def set_log_store(cls, log_store: Optional[LogStore]): + """ + Sets a Object to write the log messages to + :param LogStore log_store: + """ if log_store is not None and not isinstance(log_store, LogStore): raise TypeError("log_store must be a LogStore") cls.__log_store = log_store @@ -258,6 +262,9 @@ def process(self, msg: object, kwargs) -> Tuple[object, dict]: @classmethod def atexit_handler(cls) -> None: + """ + Adds code to print out high level log messages python run ends + """ messages = [] if cls.__log_store: try: diff --git a/spinn_utilities/make_tools/converter.py b/spinn_utilities/make_tools/converter.py index 018a0216..ae407ea3 100644 --- a/spinn_utilities/make_tools/converter.py +++ b/spinn_utilities/make_tools/converter.py @@ -80,6 +80,7 @@ def _mkdir(destination): if __name__ == '__main__': _src = sys.argv[1] _dest = sys.argv[2] + # pylint: disable=invalid-name if len(sys.argv) > 3: _new_dict = bool(sys.argv[3]) else: diff --git a/spinn_utilities/make_tools/file_converter.py b/spinn_utilities/make_tools/file_converter.py index 33a5c178..0448b217 100644 --- a/spinn_utilities/make_tools/file_converter.py +++ b/spinn_utilities/make_tools/file_converter.py @@ -385,7 +385,7 @@ def split_by_comma_plus(self, main, line_num): else: # Not a String so look for function count = self.bracket_count(part) - if (count > 0): + if count > 0: # More opening and closing brackets so in function new_part = parts.pop(i) # Keep combining parts until you find the last closing @@ -508,43 +508,43 @@ def _process_chars(self, dest_f, line_num, text): :param str text: Text of that line including whitespace :raises UnexpectedCException: """ - pos = 0 + position = 0 write_flag = 0 - while text[pos] != "\n": + while text[position] != "\n": if self._status == State.COMMENT: - if text[pos] == "*" and text[pos+1] == "/": - dest_f.write(text[write_flag:pos + 2]) - pos = pos + 2 - write_flag = pos + if text[position] == "*" and text[position+1] == "/": + dest_f.write(text[write_flag:position + 2]) + position = position + 2 + write_flag = position self._status = self._previous_status else: - pos = pos + 1 - elif text[pos] == "/": - if text[pos+1] == "*": + position = position + 1 + elif text[position] == "/": + if text[position+1] == "*": if self._status == State.IN_LOG: - self._log_full += text[write_flag:pos].strip() + self._log_full += text[write_flag:position].strip() if self._log_full[-1] == ")": self._status = State.IN_LOG_CLOSE_BRACKET # NO change to self._log_lines as newline not removed else: - dest_f.write(text[write_flag:pos]) - write_flag = pos - pos = pos + 2 # leave the /* as not written + dest_f.write(text[write_flag:position]) + write_flag = position + position = position + 2 # leave the /* as not written self._previous_status = self._status self._status = State.COMMENT - elif text[pos+1] == "/": + elif text[position+1] == "/": if self._status == State.IN_LOG: - self._log_full += text[write_flag:pos].strip() + self._log_full += text[write_flag:position].strip() # NO change to self._log_lines as newline not removed - dest_f.write(text[pos:]) + dest_f.write(text[position:]) else: dest_f.write(text[write_flag:]) return # Finished line else: - pos += 1 + position += 1 - elif text[pos] == '"': - str_pos = pos + 1 + elif text[position] == '"': + str_pos = position + 1 while text[str_pos] != '"': if text[str_pos] == "\n": raise UnexpectedCException( @@ -559,22 +559,22 @@ def _process_chars(self, dest_f, line_num, text): str_pos += 2 # ignore next char which may be a " else: str_pos += 1 - pos = str_pos + 1 + position = str_pos + 1 continue elif self._status == State.IN_LOG: - if text[pos] == ")": - match = LOG_END_REGEX.match(text[pos:]) + if text[position] == ")": + match = LOG_END_REGEX.match(text[position:]) if match: # include the end - pos = pos + len(match.group(0)) - self._log_full += text[write_flag:pos].strip() + position = position + len(match.group(0)) + self._log_full += text[write_flag:position].strip() self._status = State.NORMAL_CODE - if text[pos:].strip(): # Stuff left - write_flag = pos + if text[position:].strip(): # Stuff left + write_flag = position # self._log_lines not changed as no newline # check for a \ after the log - if text[pos:].strip() == "\\": + if text[position:].strip() == "\\": self._write_log_method(dest_f, line_num, "\\") return else: @@ -585,43 +585,43 @@ def _process_chars(self, dest_f, line_num, text): return # Finished line else: # not the require ); so continue - pos += 1 + position += 1 else: - pos += 1 + position += 1 elif self._status == State.IN_LOG_CLOSE_BRACKET: stripped = text.strip() if stripped[0] == ";": self._log_full += (";") self._write_log_method(dest_f, line_num) - pos = text.index(";") + 1 - write_flag = pos + position = text.index(";") + 1 + write_flag = position self._status = State.NORMAL_CODE else: # Save the ) as not part of the end self._status = State.IN_LOG - elif text[pos] == "l": - match = LOG_START_REGEX.match(text[pos:]) + elif text[position] == "l": + match = LOG_START_REGEX.match(text[position:]) if match: self._log_start = text.index(match.group(0)) self._log = "".join(match.group(0).split()) self._status = State.IN_LOG self._log_full = "" # text saved after while self._log_lines = 0 - dest_f.write(text[write_flag:pos]) + dest_f.write(text[write_flag:position]) # written up to not including log_start # skip to end of log instruction - pos = pos + len(match.group(0)) - write_flag = pos + position = position + len(match.group(0)) + write_flag = position else: # Not a log start after all so treat as normal test - pos += 1 + position += 1 else: - pos += 1 + position += 1 - # after while text[pos] != "\n" + # after while text[position] != "\n" if self._status == State.IN_LOG: self._log_full += text[write_flag:].strip() self._log_lines += 1 diff --git a/spinn_utilities/make_tools/log_sqllite_database.py b/spinn_utilities/make_tools/log_sqllite_database.py index e4e56298..22a0327f 100644 --- a/spinn_utilities/make_tools/log_sqllite_database.py +++ b/spinn_utilities/make_tools/log_sqllite_database.py @@ -16,6 +16,7 @@ import sqlite3 import sys import time +from typing import Optional, Tuple from spinn_utilities.abstract_context_manager import AbstractContextManager _DDL_FILE = os.path.join(os.path.dirname(__file__), "db.sql") @@ -127,7 +128,14 @@ def __clear_db(self): cursor.execute( "UPDATE SQLITE_SEQUENCE SET SEQ=0 WHERE NAME='directory'") - def get_directory_id(self, src_path, dest_path): + def get_directory_id(self, src_path: str, dest_path: str) -> int: + """ + gets the Ids for this directory. Making a new one if needed + + :param str src_path: + :param str dest_path: + :rtype: int + """ with self._db: cursor = self._db.cursor() # reuse the existing if it exists @@ -148,7 +156,14 @@ def get_directory_id(self, src_path, dest_path): """, (src_path, dest_path)) return cursor.lastrowid - def get_file_id(self, directory_id, file_name): + def get_file_id(self, directory_id: int, file_name: str) -> int: + """ + Gets the id for this file, making a new one if needed. + + :param int directory_id: + :param str file_name: + :rtype: int + """ with self._db: # Make previous one as not last with self._db: @@ -167,7 +182,16 @@ def get_file_id(self, directory_id, file_name): """, (directory_id, file_name, _timestamp())) return cursor.lastrowid - def set_log_info(self, log_level, line_num, original, file_id): + def set_log_info( + self, log_level: int, line_num: int, original: str, file_id: int): + """ + Saves the data needed to replace a short log back to the original. + + :param int log_level: + :param int line_num: + :param str original: + :param int file_id: + """ with self._db: cursor = self._db.cursor() # reuse the existing number if nothing has changed @@ -197,7 +221,13 @@ def set_log_info(self, log_level, line_num, original, file_id): """, (log_level, line_num, original, file_id)): return row["log_id"] - def get_log_info(self, log_id): + def get_log_info(self, log_id: str) -> Optional[Tuple[int, str, int, str]]: + """ + Gets the data needed to replace a short log back to the original. + + :param str log_id: The int id as a String + :rtype: tuple(int, str, int, str) + """ with self._db: for row in self._db.execute( """ @@ -208,8 +238,17 @@ def get_log_info(self, log_id): """, [log_id]): return (row["log_level"], row["file_name"], row["line_num"], row["original"]) + return None + + def check_original(self, original: str): + """ + Checks that an original log line has been added to the database. + + Mainly used for testing - def check_original(self, original): + :param str original: + :raises ValueError: If the original is not in the database + """ with self._db: for row in self._db.execute( """ @@ -221,6 +260,11 @@ def check_original(self, original): raise ValueError(f"{original} not found in database") def get_max_log_id(self): + """ + Get the max id of any log message. + + :rtype: int + """ with self._db: for row in self._db.execute( """ diff --git a/spinn_utilities/make_tools/replacer.py b/spinn_utilities/make_tools/replacer.py index 084776da..5aeca2c3 100644 --- a/spinn_utilities/make_tools/replacer.py +++ b/spinn_utilities/make_tools/replacer.py @@ -15,6 +15,7 @@ import logging import struct import sys +from typing import Optional, Tuple from spinn_utilities.log import FormatAdapter from .file_converter import FORMAT_EXP from .file_converter import TOKEN @@ -44,7 +45,7 @@ def __exit__(self, exc_type, exc_value, exc_traceback): _FLT_FMT = struct.Struct("!f") _DBL_FMT = struct.Struct("!d") - def _replace(self, short): + def _replace(self, short: str) -> Optional[Tuple[int, str, int, str]]: """ Apply the replacements to a short message. @@ -83,7 +84,13 @@ def _replace(self, short): return None return (log_level, file_name, line_num, replaced) - def replace(self, short): + def replace(self, short: str) -> str: + """ + Apply the replacements to a short message. + + :param str short: + :rtype: str + """ data = self._replace(short) if data is None: return short @@ -102,6 +109,7 @@ def _hexes_to_double(self, upper, lower): if __name__ == '__main__': encoded = sys.argv[1] + # pylint: disable=invalid-name line = "".join([c if c.isalnum() else TOKEN for c in encoded]) with Replacer() as replacer: print(replacer.replace(line)) diff --git a/spinn_utilities/matrix/demo_matrix.py b/spinn_utilities/matrix/demo_matrix.py index 8f4f9512..b5e0f654 100644 --- a/spinn_utilities/matrix/demo_matrix.py +++ b/spinn_utilities/matrix/demo_matrix.py @@ -20,8 +20,10 @@ class DemoMatrix(AbstractMatrix[T, X, Y], Generic[T, X, Y]): - __slots__ = ( - "data") + """ + Deprecated + """ + __slots__ = ["data"] def __init__(self) -> None: self.data: Dict[X, Dict[Y, T]] = defaultdict(dict) diff --git a/spinn_utilities/matrix/double_dict.py b/spinn_utilities/matrix/double_dict.py index 914cbd9e..4831b759 100644 --- a/spinn_utilities/matrix/double_dict.py +++ b/spinn_utilities/matrix/double_dict.py @@ -20,6 +20,9 @@ class DoubleDict(Generic[T, X, Y]): + """ + Deprecated + """ __slots__ = [ "_matrix", "_xtype", "_ytype"] diff --git a/spinn_utilities/ordered_set.py b/spinn_utilities/ordered_set.py index cf0dc3da..42a0c52f 100644 --- a/spinn_utilities/ordered_set.py +++ b/spinn_utilities/ordered_set.py @@ -22,6 +22,9 @@ class OrderedSet(MutableSet, Generic[T]): + """ + A set like Object where peek, pop and iterate are in insert order + """ __slots__ = ( "_map", ) @@ -51,6 +54,11 @@ def __reversed__(self) -> Iterator[T]: return self._map.__reversed__() def peek(self, last: bool = True) -> T: + """ + Retrieve the first element from the set without removing it + :param last: + :return: + """ if not self._map: # i.e., is self._map empty? raise KeyError('set is empty') if last: @@ -65,6 +73,11 @@ def __contains__(self, key: Any) -> bool: return key in self._map def update(self, iterable: Iterable[T]): + """ + Updates the set by adding each item in order + + :param iterable: + """ for item in iterable: self.add(item) @@ -75,8 +88,8 @@ def pop(self, last: bool = True) -> T: # pylint: disable=arguments-differ def __repr__(self) -> str: if not self._map: # i.e., is self._map empty? - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, list(self)) + return f'{self.__class__.__name__}()' + return f'{self.__class__.__name__}({list(self)})' def __eq__(self, other: Any) -> bool: if isinstance(other, OrderedSet): diff --git a/spinn_utilities/overrides.py b/spinn_utilities/overrides.py index 5e3f22ef..836d600f 100644 --- a/spinn_utilities/overrides.py +++ b/spinn_utilities/overrides.py @@ -19,6 +19,7 @@ Method = TypeVar("Method", bound=Callable[..., Any]) +# pylint: disable=invalid-name class overrides(object): """ A decorator for indicating that a method overrides another method in diff --git a/spinn_utilities/ping.py b/spinn_utilities/ping.py index cdb4d627..01909088 100644 --- a/spinn_utilities/ping.py +++ b/spinn_utilities/ping.py @@ -67,7 +67,7 @@ def host_is_reachable(ip_address): if ip_address in Ping.unreachable: return False tries = 0 - while (True): + while True: if Ping.ping(ip_address) == 0: return True tries += 1 diff --git a/spinn_utilities/progress_bar.py b/spinn_utilities/progress_bar.py index 7e5e14f2..a6538895 100644 --- a/spinn_utilities/progress_bar.py +++ b/spinn_utilities/progress_bar.py @@ -108,9 +108,8 @@ def _print_distance_indicator(self, description): print(" ", end="", file=self._destination) def _print_distance_line(self, first_space, second_space): - line = "{}0%{}50%{}100%{}".format( - self._end_character, " " * first_space, " " * second_space, - self._end_character) + line = f"{self._end_character}0%{' ' * first_space}50%" \ + f"{' ' * second_space}100%{self._end_character}" print(line, end="", file=self._destination) def _print_progress(self, length): @@ -271,6 +270,9 @@ def __next_line(cls): @classmethod def init_once(cls): + """ + At startup reads progress bar data from file to be used every time + """ cls._enabled = False # read in the songs once for performance reasons path = os.path.join( @@ -293,6 +295,7 @@ def init_once(cls): # clean up lines so that spaces are still visible for _seq_id in cls._step_characters: step = cls._step_characters[_seq_id] + # pylint: disable=consider-using-enumerate for _line_no in range(len(step)): step[_line_no] = step[_line_no].replace(" ", "_") diff --git a/spinn_utilities/ranged/abstract_dict.py b/spinn_utilities/ranged/abstract_dict.py index b6ffd4fc..e24eb3ba 100644 --- a/spinn_utilities/ranged/abstract_dict.py +++ b/spinn_utilities/ranged/abstract_dict.py @@ -69,7 +69,7 @@ def keys(self) -> Iterable[str]: def set_value( self, key: str, value: T, use_list_as_value: bool = False): """ - Resets a already existing key to the new value. + Sets a already existing key to the new value. All IDs in the whole range or view will have this key set. .. warning:: diff --git a/spinn_utilities/ranged/abstract_list.py b/spinn_utilities/ranged/abstract_list.py index 3f0ab846..112d32cc 100644 --- a/spinn_utilities/ranged/abstract_list.py +++ b/spinn_utilities/ranged/abstract_list.py @@ -13,11 +13,11 @@ # limitations under the License. from __future__ import annotations import numbers -import numpy -from numpy.typing import NDArray from typing import ( Any, Callable, Generic, Iterator, Optional, Sequence, Tuple, TypeVar, Union, cast) +import numpy +from numpy.typing import NDArray from typing_extensions import Self, TypeAlias from spinn_utilities.abstract_base import AbstractBase, abstractmethod from spinn_utilities.overrides import overrides @@ -336,7 +336,7 @@ def iter_by_selector(self, selector: Selector = None) -> Iterator[T]: """ # No Selector so iter all fast if selector is None: - return self.__iter__() + return iter(self) if isinstance(selector, int): # Handle negative indices diff --git a/spinn_utilities/ranged/abstract_sized.py b/spinn_utilities/ranged/abstract_sized.py index b906ac0c..39dd68af 100644 --- a/spinn_utilities/ranged/abstract_sized.py +++ b/spinn_utilities/ranged/abstract_sized.py @@ -77,7 +77,7 @@ def _check_id_in_range(self, the_id: Union[int, SupportsInt]) -> int: if not self._is_id_type(the_id): raise TypeError(f"Invalid argument type {type(the_id)}.") the_id = int(the_id) - if not (0 <= the_id < self._size): + if not 0 <= the_id < self._size: raise IndexError(f"The index {the_id} is out of range.") return the_id @@ -197,7 +197,7 @@ def selector_to_ids(self, selector: Selector, warn=False) -> Sequence[int]: :return: a (possibly sorted) list of IDs """ if _is_iterable_selector(selector): - # bool is subclass of int so if any are bools all must be + # bool is subclass of int so if any are bool all must be if any(isinstance(item, (bool, numpy.bool_)) for item in selector): if all(isinstance(item, (bool, numpy.bool_)) for item in selector): @@ -211,7 +211,7 @@ def selector_to_ids(self, selector: Selector, warn=False) -> Sequence[int]: for item in selector): # list converts any specific numpy types # blows up if someone gives an iterable of ndarray; - # serves them right for being too smartass + # serves them right for being too smart-ass ids = list(map(int, selector)) for _id in ids: if _id < 0: @@ -238,7 +238,7 @@ def selector_to_ids(self, selector: Selector, warn=False) -> Sequence[int]: return range(slice_start, slice_stop, step) if isinstance(selector, (int, numpy.integer)): - selector = int(selector) # De-numpy-fy + selector = int(selector) # make sure it is a simple int if selector < 0: selector = self._size + selector if selector < 0 or selector >= self._size: diff --git a/spinn_utilities/ranged/abstract_view.py b/spinn_utilities/ranged/abstract_view.py index de1eb234..7f626254 100644 --- a/spinn_utilities/ranged/abstract_view.py +++ b/spinn_utilities/ranged/abstract_view.py @@ -29,8 +29,7 @@ class AbstractView( # pylint: disable=abstract-method indices, and only be written to with str indices. This may change to become more permissive in future versions. """ - __slots__ = ( - "_range_dict") + __slots__ = ["_range_dict"] def __init__(self, range_dict: RangeDictionary[T]): """ diff --git a/spinn_utilities/ranged/ids_view.py b/spinn_utilities/ranged/ids_view.py index eb03b020..fa897630 100644 --- a/spinn_utilities/ranged/ids_view.py +++ b/spinn_utilities/ranged/ids_view.py @@ -71,6 +71,13 @@ def set_value(self, key: str, value: T, use_list_as_value=False): ranged_list.set_value_by_id(the_id=_id, value=value) def set_value_by_ids(self, key: str, ids: Iterable[int], value: T): + """ + Sets a already existing key to the new value. For the view specified. + + :param str key: + :param iter(int) ids: + :param value: + """ rl = self._range_dict[key] for _id in ids: rl.set_value_by_id(the_id=_id, value=value) diff --git a/spinn_utilities/ranged/multiple_values_exception.py b/spinn_utilities/ranged/multiple_values_exception.py index 93be9d14..fe59452c 100644 --- a/spinn_utilities/ranged/multiple_values_exception.py +++ b/spinn_utilities/ranged/multiple_values_exception.py @@ -14,6 +14,9 @@ class MultipleValuesException(Exception): + """ + Raised when there more than one value found unexpectedly. + """ def __init__(self, key=None, value1=None, value2=None): if key is None: diff --git a/spinn_utilities/ranged/range_dictionary.py b/spinn_utilities/ranged/range_dictionary.py index f4eb98c3..a3a544c3 100644 --- a/spinn_utilities/ranged/range_dictionary.py +++ b/spinn_utilities/ranged/range_dictionary.py @@ -118,7 +118,7 @@ def view_factory(self, key: _KeyType) -> AbstractView: # Slice is really a list of integers - change it and continue below key = range(self._size)[key] - # Key can only now be an iterable of ints so make it a list and check + # Key can only now be an int iterable so make it a list and check key = list(key) if not all(isinstance(x, int) for x in key): raise KeyError("Only list/tuple of int are supported") @@ -135,7 +135,7 @@ def view_factory(self, key: _KeyType) -> AbstractView: return _SliceView( range_dict=self, start=key[0], stop=key[-1] + 1) - # Random jumble of ints - return an IDs view + # Random jumble of int values - return an IDs view return _IdsView(range_dict=self, ids=key) @overload diff --git a/spinn_utilities/ranged/ranged_list.py b/spinn_utilities/ranged/ranged_list.py index a329f849..153d3e11 100644 --- a/spinn_utilities/ranged/ranged_list.py +++ b/spinn_utilities/ranged/ranged_list.py @@ -436,7 +436,7 @@ def set_value_by_slice( """ slice_start, slice_stop = self._check_slice_in_range( slice_start, slice_stop) - if (slice_start == slice_stop): + if slice_start == slice_stop: return # Empty list so do nothing # If the value to set is a list, set the values directly @@ -510,6 +510,13 @@ def _set_values_list(self, ids: IdsType, value: _ListType): def set_value_by_ids( self, ids: IdsType, value: _ValueType, use_list_as_value=False): + """ + Sets a already existing key to the new value. For the ids specified. + + :param str key: + :type ids: iter(int) or numpy.array + :param value: + """ if not use_list_as_value and self.is_list(value, len(ids)): self._set_values_list(ids, value) else: diff --git a/spinn_utilities/ranged/ranged_list_of_lists.py b/spinn_utilities/ranged/ranged_list_of_lists.py index 9644328f..90c03ccb 100644 --- a/spinn_utilities/ranged/ranged_list_of_lists.py +++ b/spinn_utilities/ranged/ranged_list_of_lists.py @@ -27,6 +27,9 @@ class RangedListOfList(RangedList[List[T]], Generic[T]): + """ + A Ranged object for lists of list. + """ # pylint: disable=unused-argument @overrides(RangedList.listness_check) def listness_check(self, value: _ValueType) -> bool: diff --git a/spinn_utilities/ranged/slice_view.py b/spinn_utilities/ranged/slice_view.py index 4ed4519a..94c34958 100644 --- a/spinn_utilities/ranged/slice_view.py +++ b/spinn_utilities/ranged/slice_view.py @@ -66,6 +66,12 @@ def get_value(self, key: _Keys): for k in key} def update_safe_iter_all_values(self, key: str) -> Iterable[T]: + """ + Iterate over the Values in a way that will work even between updates + + :param str key: + :rtype: iter + """ ranged_list = self._range_dict.get_list(key) for the_id in self.ids(): yield ranged_list.get_value_by_id(the_id=the_id) diff --git a/spinn_utilities/see.py b/spinn_utilities/see.py index 66e4c5a3..4f627742 100644 --- a/spinn_utilities/see.py +++ b/spinn_utilities/see.py @@ -17,6 +17,7 @@ _introspector = inspect.getfullargspec +# pylint: disable=invalid-name class see(overrides): """ A decorator for indicating that the documentation of the method diff --git a/spinn_utilities/socket_address.py b/spinn_utilities/socket_address.py index ecd2788a..dde82a27 100644 --- a/spinn_utilities/socket_address.py +++ b/spinn_utilities/socket_address.py @@ -99,6 +99,5 @@ def __hash__(self): return self.__hash def __repr__(self): - return "SocketAddress({}, {}, {})".format( - repr(self._notify_host_name), self._notify_port_no, - self._listen_port) + return (f"SocketAddress({repr(self._notify_host_name)}, " + f"{self._notify_port_no}, {self._listen_port})") diff --git a/spinn_utilities/typing/json.py b/spinn_utilities/typing/json.py index 6de40507..eadbe518 100644 --- a/spinn_utilities/typing/json.py +++ b/spinn_utilities/typing/json.py @@ -23,7 +23,7 @@ JsonValue: TypeAlias = Union[int, float, str, None, "JsonObject", "JsonArray"] # NB: The above type is mutually recursive with the definitions below. -#: The tyoe of JSON objects. +#: The type of JSON objects. JsonObject: TypeAlias = Dict[str, JsonValue] #: The type of JSON arrays. diff --git a/unittests/test_conf_loader.py b/unittests/test_conf_loader.py index 670cb222..285b9827 100644 --- a/unittests/test_conf_loader.py +++ b/unittests/test_conf_loader.py @@ -243,7 +243,7 @@ def test_logging(tmpdir, not_there): def test_errors(not_there): name, place = not_there with pytest.raises(ConfigTemplateException): - conf_loader.install_cfg_and_IOError( + conf_loader.install_cfg_and_error( filename=name, defaults=[], config_locations=[])