Sat, 26 Apr 2025 12:34:32 +0200
MicroPython
- Added a configuration option to disable the support for the no longer produced Pimoroni Pico Wireless Pack.
# -*- coding: utf-8 -*- # Copyright (c) 2021 - 2025 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing a checker for import statements. """ import ast import re from CodeStyleTopicChecker import CodeStyleTopicChecker class NameOrderChecker(CodeStyleTopicChecker): """ Class implementing a checker for name ordering. Note: Name ordering is checked for import statements, the '__all__' statement and exception names of exception handlers. """ Codes = [ ## Imports order "NO-101", "NO-102", "NO-103", "NO-104", "NO-105", ] Category = "NO" def __init__(self, source, filename, tree, select, ignore, expected, repeat, args): """ Constructor @param source source code to be checked @type list of str @param filename name of the source file @type str @param tree AST tree of the source code @type ast.Module @param select list of selected codes @type list of str @param ignore list of codes to be ignored @type list of str @param expected list of expected codes @type list of str @param repeat flag indicating to report each occurrence of a code @type bool @param args dictionary of arguments for the various checks @type dict """ super().__init__( NameOrderChecker.Category, source, filename, tree, select, ignore, expected, repeat, args, ) # parameters for import sorting if args["SortOrder"] == "native": self.__sortingFunction = sorted else: # naturally is the default sort order self.__sortingFunction = self.__naturally self.__sortCaseSensitive = args["SortCaseSensitive"] checkersWithCodes = [ (self.__checkNameOrder, ("NO-101", "NO-102", "NO-103", "NO-104", "NO-105")), ] self._initializeCheckers(checkersWithCodes) ####################################################################### ## Name Order ## ## adapted from: flake8-alphabetize v0.0.21 ####################################################################### def __checkNameOrder(self): """ Private method to check the order of import statements and handled exceptions. """ from .ImportNode import ImportNode errors = [] imports = [] importNodes, aListNode, eListNodes = self.__findNodes(self.tree) # check for an error in '__all__' allError = self.__findErrorInAll(aListNode) if allError is not None: errors.append(allError) errors.extend(self.__findExceptionListErrors(eListNodes)) for importNode in importNodes: if isinstance(importNode, ast.Import) and len(importNode.names) > 1: # skip suck imports because its already handled by pycodestyle continue imports.append( ImportNode( self.args.get("ApplicationPackageNames", []), importNode, self, self.args.get("SortIgnoringStyle", False), self.args.get("SortFromFirst", False), ) ) lenImports = len(imports) if lenImports > 0: p = imports[0] if p.error is not None: errors.append(p.error) if lenImports > 1: for n in imports[1:]: if n.error is not None: errors.append(n.error) if n == p: if self.args.get("CombinedAsImports", False) or ( not n.asImport and not p.asImport ): errors.append((n.node, "NO-103", str(p), str(n))) elif n < p: errors.append((n.node, "NO-101", str(n), str(p))) p = n for error in errors: self.addErrorFromNode(error[0], error[1], *error[2:]) def __findExceptionListNodes(self, tree): """ Private method to find all exception types handled by given tree. @param tree reference to the ast node tree to be parsed @type ast.AST @return list of exception types @rtype list of ast.Name """ nodes = [] for node in ast.walk(tree): if isinstance(node, ast.ExceptHandler): nodeType = node.type if isinstance(nodeType, (ast.List, ast.Tuple)): nodes.append(nodeType) return nodes def __findNodes(self, tree): """ Private method to find all import and import from nodes of the given tree. @param tree reference to the ast node tree to be parsed @type ast.AST @return tuple containing a list of import nodes, the '__all__' node and exception nodes @rtype tuple of (ast.Import | ast.ImportFrom, ast.List | ast.Tuple, ast.List | ast.Tuple) """ importNodes = [] aListNode = None eListNodes = self.__findExceptionListNodes(tree) if isinstance(tree, ast.Module): body = tree.body for n in body: if isinstance(n, (ast.Import, ast.ImportFrom)): importNodes.append(n) elif isinstance(n, ast.Assign): for t in n.targets: if isinstance(t, ast.Name) and t.id == "__all__": value = n.value if isinstance(value, (ast.List, ast.Tuple)): aListNode = value return importNodes, aListNode, eListNodes def __findErrorInAll(self, node): """ Private method to check the '__all__' node for errors. @param node reference to the '__all__' node @type ast.List or ast.Tuple @return tuple containing a reference to the node, an error code and the error arguments @rtype tuple of (ast.List | ast.Tuple, str, str) """ if node is not None: actualList = [] for el in node.elts: if isinstance(el, ast.Constant): actualList.append(el.value) else: # Can't handle anything that isn't a string literal return None expectedList = self.sorted( actualList, key=lambda k: self.moduleKey(k, subImports=True), ) if expectedList != actualList: return (node, "NO-104", ", ".join(expectedList)) return None def __findExceptionListStr(self, node): """ Private method to get the exception name out of an exception handler type node. @param node node to be treated @type ast.Name or ast.Attribute @return string containing the exception name @rtype str """ if isinstance(node, ast.Name): return node.id elif isinstance(node, ast.Attribute): return f"{self.__findExceptionListStr(node.value)}.{node.attr}" return "" def __findExceptionListErrors(self, nodes): """ Private method to check the exception node for errors. @param nodes list of exception nodes @type list of ast.List or ast.Tuple @return DESCRIPTION @rtype TYPE """ errors = [] for node in nodes: actualList = [self.__findExceptionListStr(elt) for elt in node.elts] expectedList = self.sorted(actualList) if expectedList != actualList: errors.append((node, "NO-105", ", ".join(expectedList))) return errors def sorted(self, toSort, key=None, reverse=False): """ Public method to sort the given list of names. @param toSort list of names to be sorted @type list of str @param key function to generate keys (defaults to None) @type function (optional) @param reverse flag indicating a reverse sort (defaults to False) @type bool (optional) @return sorted list of names @rtype list of str """ return self.__sortingFunction(toSort, key=key, reverse=reverse) def __naturally(self, toSort, key=None, reverse=False): """ Private method to sort the given list of names naturally. Note: Natural sorting maintains the sort order of numbers (i.e. [Q1, Q10, Q2] is sorted as [Q1, Q2, Q10] while the Python standard sort would yield [Q1, Q10, Q2]. @param toSort list of names to be sorted @type list of str @param key function to generate keys (defaults to None) @type function (optional) @param reverse flag indicating a reverse sort (defaults to False) @type bool (optional) @return sorted list of names @rtype list of str """ if key is None: keyCallback = self.__naturalKeys else: def keyCallback(text): return self.__naturalKeys(key(text)) return sorted(toSort, key=keyCallback, reverse=reverse) def __atoi(self, text): """ Private method to convert the given text to an integer number. @param text text to be converted @type str @return integer number @rtype int """ return int(text) if text.isdigit() else text def __naturalKeys(self, text): """ Private method to generate keys for natural sorting. @param text text to generate a key for @type str @return key for natural sorting @rtype list of str or int """ return [self.__atoi(c) for c in re.split(r"(\d+)", text)] def moduleKey(self, moduleName, subImports=False): """ Public method to generate a key for the given module name. @param moduleName module name @type str @param subImports flag indicating a sub import like in 'from foo import bar, baz' (defaults to False) @type bool (optional) @return generated key @rtype str """ prefix = "" if subImports: if moduleName.isupper() and len(moduleName) > 1: prefix = "A" elif moduleName[0:1].isupper(): prefix = "B" else: prefix = "C" if not self.__sortCaseSensitive: moduleName = moduleName.lower() return f"{prefix}{moduleName}"