From 2c202f087fa403d24b31d9e65f9992b9120de29c Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Mon, 1 Jul 2024 22:50:44 +0200 Subject: [PATCH] stubber: release v1.23.1 --- mip/full.json | 2 +- mip/minified.json | 2 +- mip/mpy_v5.json | 2 +- mip/mpy_v6.json | 2 +- package.json | 2 +- poetry.lock | 8 +- pyproject.toml | 4 +- src/mpflash/pyproject.toml | 2 +- src/stubber/__init__.py | 2 +- src/stubber/board/createstubs.py | 8 +- src/stubber/board/createstubs_db.py | 10 +- src/stubber/board/createstubs_db_min.py | 1151 ++++++------------- src/stubber/board/createstubs_db_mpy.mpy | Bin 9237 -> 9536 bytes src/stubber/board/createstubs_mem.py | 10 +- src/stubber/board/createstubs_mem_min.py | 1067 +++++------------ src/stubber/board/createstubs_mem_mpy.mpy | Bin 8851 -> 9118 bytes src/stubber/board/createstubs_min.py | 1277 +++++---------------- src/stubber/board/createstubs_mpy.mpy | Bin 12241 -> 12388 bytes 18 files changed, 953 insertions(+), 2596 deletions(-) diff --git a/mip/full.json b/mip/full.json index 63910a46..90e86ef5 100644 --- a/mip/full.json +++ b/mip/full.json @@ -18,5 +18,5 @@ ] ], "deps": [], - "version": "1.23.0" + "version": "1.23.1" } diff --git a/mip/minified.json b/mip/minified.json index 0373b2a0..cb50a944 100644 --- a/mip/minified.json +++ b/mip/minified.json @@ -18,5 +18,5 @@ ] ], "deps": [], - "version": "1.23.0" + "version": "1.23.1" } diff --git a/mip/mpy_v5.json b/mip/mpy_v5.json index 53240f39..116a1fd4 100644 --- a/mip/mpy_v5.json +++ b/mip/mpy_v5.json @@ -18,5 +18,5 @@ ] ], "deps": [], - "version": "1.23.0" + "version": "1.23.1" } diff --git a/mip/mpy_v6.json b/mip/mpy_v6.json index 25a94716..aea6f7cd 100644 --- a/mip/mpy_v6.json +++ b/mip/mpy_v6.json @@ -18,5 +18,5 @@ ] ], "deps": [], - "version": "1.23.0" + "version": "1.23.1" } diff --git a/package.json b/package.json index 0373b2a0..cb50a944 100644 --- a/package.json +++ b/package.json @@ -18,5 +18,5 @@ ] ], "deps": [], - "version": "1.23.0" + "version": "1.23.1" } diff --git a/poetry.lock b/poetry.lock index 02ee8d48..b5c4ac8a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1490,13 +1490,13 @@ test = ["pytest (<5.4)", "pytest-cov"] [[package]] name = "mpflash" -version = "0.9.1" +version = "0.9.1.post2" description = "Flash and download tool for MicroPython firmwares" optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "mpflash-0.9.1-py3-none-any.whl", hash = "sha256:1bfc8f3a493b57a2e592351d7346ed0a1e27f98741fc53cbc702e84ffcf580b1"}, - {file = "mpflash-0.9.1.tar.gz", hash = "sha256:f0d51c6f713892eeebcd4fc0fdeb91e5ba4d2da4b2238404e990a61b6d6c2fdd"}, + {file = "mpflash-0.9.1.post2-py3-none-any.whl", hash = "sha256:a45e4106a3f6fd5d2273178ac8628895395a6abf396ab1eecd88d66549927662"}, + {file = "mpflash-0.9.1.post2.tar.gz", hash = "sha256:69d237d4468077d26da15c7b30c87e4300c20f00fc80f2a30ce8f80e92faeff7"}, ] [package.dependencies] @@ -3177,4 +3177,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "4417edbf5e75fe724353fe63b1ea61483834afc4149fcde33b5f46d5d3b7e49d" +content-hash = "797507808e1046b170b4d4b8650d3e295c3c5b8cf9698646874a83fc3b529757" diff --git a/pyproject.toml b/pyproject.toml index 36a0f6bc..f9de9562 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ repo-path = "./repos" [tool.poetry] name = "micropython-stubber" -version = "1.23.0" +version = "1.23.1" description = "Tooling to create and maintain stubs for MicroPython" authors = ["Jos Verlinde "] license = "MIT" @@ -49,7 +49,7 @@ python = ">=3.9,<4.0" python-minifier = { version = "^2.7.0", python = "<3.12" } # no support for 3.12 yet requests = "^2.32.3" mypy = "1.9.0" -mpflash = "0.9.1" +mpflash = "0.9.1.post2" mpremote = "^1.23.0" # others autoflake = ">=1.7,<3.0" diff --git a/src/mpflash/pyproject.toml b/src/mpflash/pyproject.toml index 9a44d32e..01267d0b 100644 --- a/src/mpflash/pyproject.toml +++ b/src/mpflash/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "mpflash" -version = "0.9.1.Post1" +version = "0.9.1.Post2" description = "Flash and download tool for MicroPython firmwares" authors = ["Jos Verlinde "] license = "MIT" diff --git a/src/stubber/__init__.py b/src/stubber/__init__.py index 8a2c8571..ebbbb3e0 100644 --- a/src/stubber/__init__.py +++ b/src/stubber/__init__.py @@ -1,3 +1,3 @@ """get the version""" -__version__ = "1.23.0" +__version__ = "1.23.1" diff --git a/src/stubber/board/createstubs.py b/src/stubber/board/createstubs.py index 8c6410c4..04daff89 100644 --- a/src/stubber/board/createstubs.py +++ b/src/stubber/board/createstubs.py @@ -24,7 +24,7 @@ except ImportError: from ucollections import OrderedDict # type: ignore -__version__ = "v1.23.0" +__version__ = "v1.23.1" ENOENT = 2 _MAX_CLASS_LEVEL = 2 # Max class nesting LIBS = ["lib", "/lib", "/sd/lib", "/flash/lib", "."] @@ -488,7 +488,7 @@ def ensure_folder(path: str): def _build(s): # extract build from sys.version or os.uname().version if available - # sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f' + # sys.version: 'MicroPython v1.23.1-preview.6.g3d0b6276f' # sys.implementation.version: 'v1.13-103-gb137d064e' if not s: return "" @@ -595,10 +595,10 @@ def _info(): # type:() -> dict[str, str] if ( info["version"] and info["version"].endswith(".0") - and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.23.0 do not have a micro .0 + and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.23.1 do not have a micro .0 and info["version"] <= "1.19.9" ): - # versions from 1.10.0 to 1.23.0 do not have a micro .0 + # versions from 1.10.0 to 1.23.1 do not have a micro .0 info["version"] = info["version"][:-2] # spell-checker: disable diff --git a/src/stubber/board/createstubs_db.py b/src/stubber/board/createstubs_db.py index 634433a4..51991e4b 100644 --- a/src/stubber/board/createstubs_db.py +++ b/src/stubber/board/createstubs_db.py @@ -18,7 +18,7 @@ - cross compilation, using mpy-cross, to avoid the compilation step on the micropython device -This variant was generated from createstubs.py by micropython-stubber v1.23.0 +This variant was generated from createstubs.py by micropython-stubber v1.23.1 """ # Copyright (c) 2019-2024 Jos Verlinde @@ -43,7 +43,7 @@ except ImportError: from ucollections import OrderedDict # type: ignore -__version__ = "v1.23.0" +__version__ = "v1.23.1" ENOENT = 2 _MAX_CLASS_LEVEL = 2 # Max class nesting LIBS = ["lib", "/lib", "/sd/lib", "/flash/lib", "."] @@ -501,7 +501,7 @@ def ensure_folder(path: str): def _build(s): # extract build from sys.version or os.uname().version if available - # sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f' + # sys.version: 'MicroPython v1.23.1-preview.6.g3d0b6276f' # sys.implementation.version: 'v1.13-103-gb137d064e' if not s: return "" @@ -606,10 +606,10 @@ def _info(): # type:() -> dict[str, str] if ( info["version"] and info["version"].endswith(".0") - and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.23.0 do not have a micro .0 + and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.23.1 do not have a micro .0 and info["version"] <= "1.19.9" ): - # versions from 1.10.0 to 1.23.0 do not have a micro .0 + # versions from 1.10.0 to 1.23.1 do not have a micro .0 info["version"] = info["version"][:-2] # spell-checker: disable diff --git a/src/stubber/board/createstubs_db_min.py b/src/stubber/board/createstubs_db_min.py index 634433a4..8a26f9c0 100644 --- a/src/stubber/board/createstubs_db_min.py +++ b/src/stubber/board/createstubs_db_min.py @@ -1,825 +1,332 @@ -""" -Create stubs for (all) modules on a MicroPython board. - - This variant of the createstubs.py script is optimized for use on very-low-memory devices. - Note: this version has undergone limited testing. - - 1) reads the list of modules from a text file `modulelist.txt` that should be uploaded to the device. - 2) stored the already processed modules in a text file `modulelist.done` - 3) process the modules in the database: - - stub the module - - update the modulelist.done file - - reboots the device if it runs out of memory - 4) creates the modules.json - - If that cannot be found then only a single module (micropython) is stubbed. - In order to run this on low-memory devices two additional steps are recommended: - - minification, using python-minifierto reduce overall size, and remove logging overhead. - - cross compilation, using mpy-cross, to avoid the compilation step on the micropython device - - -This variant was generated from createstubs.py by micropython-stubber v1.23.0 -""" - -# Copyright (c) 2019-2024 Jos Verlinde - -import gc -import os -import sys +A2='No report file' +A1='Failed to create the report.' +A0='method' +z='function' +y='bool' +x='str' +w='float' +v='int' +u='micropython' +t='stubber' +s=Exception +r=KeyError +q=sorted +p=MemoryError +o=NotImplementedError +k=',\n' +j='modules.json' +i='{}/{}' +h='w' +g='dict' +f='list' +e='tuple' +d=TypeError +c=str +b=repr +W='-preview' +V='-' +U='board' +T=IndexError +S=print +R=True +Q='family' +P=len +O=ImportError +N=dir +M=open +K='port' +J='.' +I=AttributeError +H=False +G='/' +E=None +D=OSError +C='version' +B='' +import gc as F,os,sys from time import sleep - -try: - from ujson import dumps -except: - from json import dumps - -try: - from machine import reset # type: ignore -except ImportError: - pass - -try: - from collections import OrderedDict -except ImportError: - from ucollections import OrderedDict # type: ignore - -__version__ = "v1.23.0" -ENOENT = 2 -_MAX_CLASS_LEVEL = 2 # Max class nesting -LIBS = ["lib", "/lib", "/sd/lib", "/flash/lib", "."] - - -# our own logging module to avoid dependency on and interfering with logging module -class logging: - # DEBUG = 10 - INFO = 20 - WARNING = 30 - ERROR = 40 - level = INFO - prnt = print - - @staticmethod - def getLogger(name): - return logging() - - @classmethod - def basicConfig(cls, level): - cls.level = level - - # def debug(self, msg): - # if self.level <= logging.DEBUG: - # self.prnt("DEBUG :", msg) - - def info(self, msg): - if self.level <= logging.INFO: - self.prnt("INFO :", msg) - - def warning(self, msg): - if self.level <= logging.WARNING: - self.prnt("WARN :", msg) - - def error(self, msg): - if self.level <= logging.ERROR: - self.prnt("ERROR :", msg) - - -log = logging.getLogger("stubber") -logging.basicConfig(level=logging.INFO) -# logging.basicConfig(level=logging.DEBUG) - - +try:from ujson import dumps +except:from json import dumps +try:from machine import reset +except O:pass +try:from collections import OrderedDict as l +except O:from ucollections import OrderedDict as l +__version__='v1.23.1' +A3=2 +A4=2 +A5=['lib','/lib','/sd/lib','/flash/lib',J] +class L: + INFO=20;WARNING=30;ERROR=40;level=INFO;prnt=S + @staticmethod + def getLogger(name):return L() + @classmethod + def basicConfig(A,level):A.level=level + def info(A,msg): + if A.level<=L.INFO:A.prnt('INFO :',msg) + def warning(A,msg): + if A.level<=L.WARNING:A.prnt('WARN :',msg) + def error(A,msg): + if A.level<=L.ERROR:A.prnt('ERROR :',msg) +A=L.getLogger(t) +L.basicConfig(level=L.INFO) class Stubber: - "Generate stubs for modules in firmware" - - def __init__(self, path: str = None, firmware_id: str = None): # type: ignore - try: - if os.uname().release == "1.13.0" and os.uname().version < "v1.13-103": # type: ignore - raise NotImplementedError("MicroPython 1.13.0 cannot be stubbed") - except AttributeError: - pass # Allow testing on CPython 3.11 - self.info = _info() - log.info("Port: {}".format(self.info["port"])) - log.info("Board: {}".format(self.info["board"])) - gc.collect() - if firmware_id: - self._fwid = firmware_id.lower() - else: - if self.info["family"] == "micropython": - self._fwid = "{family}-v{version}-{port}-{board}".format(**self.info).rstrip("-") - else: - self._fwid = "{family}-v{version}-{port}".format(**self.info) - self._start_free = gc.mem_free() # type: ignore - - if path: - if path.endswith("/"): - path = path[:-1] - else: - path = get_root() - - self.path = "{}/stubs/{}".format(path, self.flat_fwid).replace("//", "/") - # log.debug(self.path) - try: - ensure_folder(path + "/") - except OSError: - log.error("error creating stub folder {}".format(path)) - self.problematic = [ - "upip", - "upysh", - "webrepl_setup", - "http_client", - "http_client_ssl", - "http_server", - "http_server_ssl", - ] - self.excluded = [ - "webrepl", - "_webrepl", - "port_diag", - "example_sub_led.py", - "example_pub_button.py", - ] - # there is no option to discover modules from micropython, list is read from an external file. - self.modules = [] # type: list[str] - self._json_name = None - self._json_first = False - - def get_obj_attributes(self, item_instance: object): - "extract information of the objects members and attributes" - # name_, repr_(value), type as text, item_instance - _result = [] - _errors = [] - # log.debug("get attributes {} {}".format(repr(item_instance), item_instance)) - for name in dir(item_instance): - if name.startswith("__") and not name in self.modules: - continue - # log.debug("get attribute {}".format(name)) - try: - val = getattr(item_instance, name) - # name , item_repr(value) , type as text, item_instance, order - # log.debug("attribute {}:{}".format(name, val)) - try: - type_text = repr(type(val)).split("'")[1] - except IndexError: - type_text = "" - if type_text in {"int", "float", "str", "bool", "tuple", "list", "dict"}: - order = 1 - elif type_text in {"function", "method"}: - order = 2 - elif type_text in ("class"): - order = 3 - else: - order = 4 - _result.append((name, repr(val), repr(type(val)), val, order)) - except AttributeError as e: - _errors.append("Couldn't get attribute '{}' from object '{}', Err: {}".format(name, item_instance, e)) - except MemoryError as e: - print("MemoryError: {}".format(e)) - sleep(1) - reset() - - # remove internal __ - # _result = sorted([i for i in _result if not (i[0].startswith("_"))], key=lambda x: x[4]) - _result = sorted([i for i in _result if not (i[0].startswith("__"))], key=lambda x: x[4]) - gc.collect() - return _result, _errors - - def add_modules(self, modules): - "Add additional modules to be exported" - self.modules = sorted(set(self.modules) | set(modules)) - - def create_all_stubs(self): - "Create stubs for all configured modules" - log.info("Start micropython-stubber {} on {}".format(__version__, self._fwid)) - self.report_start() - gc.collect() - for module_name in self.modules: - self.create_one_stub(module_name) - self.report_end() - log.info("Finally done") - - def create_one_stub(self, module_name: str): - if module_name in self.problematic: - log.warning("Skip module: {:<25} : Known problematic".format(module_name)) - return False - if module_name in self.excluded: - log.warning("Skip module: {:<25} : Excluded".format(module_name)) - return False - - file_name = "{}/{}.pyi".format(self.path, module_name.replace(".", "/")) - gc.collect() - result = False - try: - result = self.create_module_stub(module_name, file_name) - except OSError: - return False - gc.collect() - return result - - def create_module_stub(self, module_name: str, file_name: str = None) -> bool: # type: ignore - """Create a Stub of a single python module - - Args: - - module_name (str): name of the module to document. This module will be imported. - - file_name (Optional[str]): the 'path/filename.pyi' to write to. If omitted will be created based on the module name. - """ - if file_name is None: - fname = module_name.replace(".", "_") + ".pyi" - file_name = self.path + "/" + fname - else: - fname = file_name.split("/")[-1] - - if "/" in module_name: - # for nested modules - module_name = module_name.replace("/", ".") - - # import the module (as new_module) to examine it - new_module = None - try: - new_module = __import__(module_name, None, None, ("*")) - m1 = gc.mem_free() # type: ignore - log.info("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1)) - - except ImportError: - # log.debug("Skip module: {:<25} {:<79}".format(module_name, "Module not found.")) - return False - - # Start a new file - ensure_folder(file_name) - with open(file_name, "w") as fp: - info_ = str(self.info).replace("OrderedDict(", "").replace("})", "}") - s = '"""\nModule: \'{0}\' on {1}\n"""\n# MCU: {2}\n# Stubber: {3}\n'.format(module_name, self._fwid, info_, __version__) - fp.write(s) - fp.write("from __future__ import annotations\nfrom typing import Any, Generator\nfrom _typeshed import Incomplete\n\n") - self.write_object_stub(fp, new_module, module_name, "") - - self.report_add(module_name, file_name) - - if module_name not in {"os", "sys", "logging", "gc"}: - # try to unload the module unless we use it - try: - del new_module - except (OSError, KeyError): # lgtm [py/unreachable-statement] - log.warning("could not del new_module") - # do not try to delete from sys.modules - most times it does not work anyway - gc.collect() - return True - - def write_object_stub(self, fp, object_expr: object, obj_name: str, indent: str, in_class: int = 0): - "Write a module/object stub to an open file. Can be called recursive." - gc.collect() - if object_expr in self.problematic: - log.warning("SKIPPING problematic module:{}".format(object_expr)) - return - - # # log.debug("DUMP : {}".format(object_expr)) - items, errors = self.get_obj_attributes(object_expr) - - if errors: - log.error(errors) - - for item_name, item_repr, item_type_txt, item_instance, _ in items: - # name_, repr_(value), type as text, item_instance, order - if item_name in ["classmethod", "staticmethod", "BaseException", "Exception"]: - # do not create stubs for these primitives - continue - if item_name[0].isdigit(): - log.warning("NameError: invalid name {}".format(item_name)) - continue - # Class expansion only on first 3 levels (bit of a hack) - if ( - item_type_txt == "" - and len(indent) <= _MAX_CLASS_LEVEL * 4 - # and not obj_name.endswith(".Pin") - # avoid expansion of Pin.cpu / Pin.board to avoid crashes on most platforms - ): - # log.debug("{0}class {1}:".format(indent, item_name)) - superclass = "" - is_exception = ( - item_name.endswith("Exception") - or item_name.endswith("Error") - or item_name - in [ - "KeyboardInterrupt", - "StopIteration", - "SystemExit", - ] - ) - if is_exception: - superclass = "Exception" - s = "\n{}class {}({}):\n".format(indent, item_name, superclass) - # s += indent + " ''\n" - if is_exception: - s += indent + " ...\n" - fp.write(s) - continue - # write classdef - fp.write(s) - # first write the class literals and methods - # log.debug("# recursion over class {0}".format(item_name)) - self.write_object_stub( - fp, - item_instance, - "{0}.{1}".format(obj_name, item_name), - indent + " ", - in_class + 1, - ) - # end with the __init__ method to make sure that the literals are defined - # Add __init__ - s = indent + " def __init__(self, *argv, **kwargs) -> None:\n" - s += indent + " ...\n\n" - fp.write(s) - elif any(word in item_type_txt for word in ["method", "function", "closure"]): - # log.debug("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name)) - # module Function or class method - # will accept any number of params - # return type Any/Incomplete - ret = "Incomplete" - first = "" - # Self parameter only on class methods/functions - if in_class > 0: - first = "self, " - # class method - add function decoration - if "bound_method" in item_type_txt or "bound_method" in item_repr: - s = "{}@classmethod\n".format(indent) + "{}def {}(cls, *args, **kwargs) -> {}:\n".format(indent, item_name, ret) - else: - s = "{}def {}({}*args, **kwargs) -> {}:\n".format(indent, item_name, first, ret) - s += indent + " ...\n\n" - fp.write(s) - # log.debug("\n" + s) - elif item_type_txt == "": - # Skip imported modules - # fp.write("# import {}\n".format(item_name)) - pass - - elif item_type_txt.startswith("" - if " at " in item_repr: - item_repr = item_repr.split(" at ")[0] + " at ...>" - s = "{0}{1}: {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr) - fp.write(s) - # log.debug("\n" + s) - else: - # keep only the name - # log.debug("# all other, type = '{0}'".format(item_type_txt)) - fp.write("# all other, type = '{0}'\n".format(item_type_txt)) - - fp.write(indent + item_name + " # type: Incomplete\n") - - # del items - # del errors - # try: - # del item_name, item_repr, item_type_txt, item_instance # type: ignore - # except (OSError, KeyError, NameError): - # pass - - @property - def flat_fwid(self): - "Turn _fwid from 'v1.2.3' into '1_2_3' to be used in filename" - s = self._fwid - # path name restrictions - chars = " .()/\\:$" - for c in chars: - s = s.replace(c, "_") - return s - - def clean(self, path: str = None): # type: ignore - "Remove all files from the stub folder" - if path is None: - path = self.path - log.info("Clean/remove files in folder: {}".format(path)) - try: - os.stat(path) # TEMP workaround mpremote listdir bug - - items = os.listdir(path) - except (OSError, AttributeError): - # os.listdir fails on unix - return - for fn in items: - item = "{}/{}".format(path, fn) - try: - os.remove(item) - except OSError: - try: # folder - self.clean(item) - os.rmdir(item) - except OSError: - pass - - def report_start(self, filename: str = "modules.json"): - """Start a report of the modules that have been stubbed - "create json with list of exported modules""" - self._json_name = "{}/{}".format(self.path, filename) - self._json_first = True - ensure_folder(self._json_name) - log.info("Report file: {}".format(self._json_name)) - gc.collect() - try: - # write json by node to reduce memory requirements - with open(self._json_name, "w") as f: - f.write("{") - f.write(dumps({"firmware": self.info})[1:-1]) - f.write(",\n") - f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1]) - f.write(",\n") - f.write('"modules" :[\n') - - except OSError as e: - log.error("Failed to create the report.") - self._json_name = None - raise e - - def report_add(self, module_name: str, stub_file: str): - "Add a module to the report" - # write json by node to reduce memory requirements - if not self._json_name: - raise Exception("No report file") - try: - with open(self._json_name, "a") as f: - if not self._json_first: - f.write(",\n") - else: - self._json_first = False - line = '{{"module": "{}", "file": "{}"}}'.format(module_name, stub_file.replace("\\", "/")) - f.write(line) - - except OSError: - log.error("Failed to create the report.") - - def report_end(self): - if not self._json_name: - raise Exception("No report file") - with open(self._json_name, "a") as f: - f.write("\n]}") - # is used as sucess indicator - log.info("Path: {}".format(self.path)) - - -def ensure_folder(path: str): - "Create nested folders if needed" - i = start = 0 - while i != -1: - i = path.find("/", start) - if i != -1: - p = path[0] if i == 0 else path[:i] - # p = partial folder - try: - _ = os.stat(p) - except OSError as e: - # folder does not exist - if e.args[0] == ENOENT: - try: - os.mkdir(p) - except OSError as e2: - log.error("failed to create folder {}".format(p)) - raise e2 - # next level deep - start = i + 1 - - -def _build(s): - # extract build from sys.version or os.uname().version if available - # sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f' - # sys.implementation.version: 'v1.13-103-gb137d064e' - if not s: - return "" - s = s.split(" on ", 1)[0] if " on " in s else s - if s.startswith("v"): - if not "-" in s: - return "" - b = s.split("-")[1] - return b - if not "-preview" in s: - return "" - b = s.split("-preview")[1].split(".")[1] - return b - - -def _info(): # type:() -> dict[str, str] - try: - fam = sys.implementation[0] # type: ignore - except TypeError: - # testing on CPython 3.11 - fam = sys.implementation.name - - info = OrderedDict( - { - "family": fam, - "version": "", - "build": "", - "ver": "", - "port": sys.platform, # port: esp32 / win32 / linux / stm32 - "board": "UNKNOWN", - "cpu": "", - "mpy": "", - "arch": "", - } - ) - # change port names to be consistent with the repo - if info["port"].startswith("pyb"): - info["port"] = "stm32" - elif info["port"] == "win32": - info["port"] = "windows" - elif info["port"] == "linux": - info["port"] = "unix" - try: - info["version"] = version_str(sys.implementation.version) # type: ignore - except AttributeError: - pass - try: - _machine = sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore - # info["board"] = "with".join(_machine.split("with")[:-1]).strip() - info["board"] = _machine - info["cpu"] = _machine.split("with")[-1].strip() - info["mpy"] = ( - sys.implementation._mpy # type: ignore - if "_mpy" in dir(sys.implementation) - else sys.implementation.mpy if "mpy" in dir(sys.implementation) else "" # type: ignore - ) - except (AttributeError, IndexError): - pass - info["board"] = get_boardname() - - try: - if "uname" in dir(os): # old - # extract build from uname().version if available - info["build"] = _build(os.uname()[3]) # type: ignore - if not info["build"]: - # extract build from uname().release if available - info["build"] = _build(os.uname()[2]) # type: ignore - elif "version" in dir(sys): # new - # extract build from sys.version if available - info["build"] = _build(sys.version) - except (AttributeError, IndexError, TypeError): - pass - # avoid build hashes - # if info["build"] and len(info["build"]) > 5: - # info["build"] = "" - - if info["version"] == "" and sys.platform not in ("unix", "win32"): - try: - u = os.uname() # type: ignore - info["version"] = u.release - except (IndexError, AttributeError, TypeError): - pass - # detect families - for fam_name, mod_name, mod_thing in [ - ("pycopy", "pycopy", "const"), - ("pycom", "pycom", "FAT"), - ("ev3-pybricks", "pybricks.hubs", "EV3Brick"), - ]: - try: - _t = __import__(mod_name, None, None, (mod_thing)) - info["family"] = fam_name - del _t - break - except (ImportError, KeyError): - pass - - if info["family"] == "ev3-pybricks": - info["release"] = "2.0.0" - - if info["family"] == "micropython": - info["version"] - if ( - info["version"] - and info["version"].endswith(".0") - and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.23.0 do not have a micro .0 - and info["version"] <= "1.19.9" - ): - # versions from 1.10.0 to 1.23.0 do not have a micro .0 - info["version"] = info["version"][:-2] - - # spell-checker: disable - if "mpy" in info and info["mpy"]: # mpy on some v1.11+ builds - sys_mpy = int(info["mpy"]) - # .mpy architecture - arch = [ - None, - "x86", - "x64", - "armv6", - "armv6m", - "armv7m", - "armv7em", - "armv7emsp", - "armv7emdp", - "xtensa", - "xtensawin", - ][sys_mpy >> 10] - if arch: - info["arch"] = arch - # .mpy version.minor - info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3) - if info["build"] and not info["version"].endswith("-preview"): - info["version"] = info["version"] + "-preview" - # simple to use version[-build] string - info["ver"] = f"{info['version']}-{info['build']}" if info["build"] else f"{info['version']}" - - return info - - -def version_str(version: tuple): # -> str: - v_str = ".".join([str(n) for n in version[:3]]) - if len(version) > 3 and version[3]: - v_str += "-" + version[3] - return v_str - - -def get_boardname() -> str: - "Read the board name from the boardname.py file that may have been created upfront" - try: - from boardname import BOARDNAME # type: ignore - - log.info("Found BOARDNAME: {}".format(BOARDNAME)) - except ImportError: - log.warning("BOARDNAME not found") - BOARDNAME = "" - return BOARDNAME - - -def get_root() -> str: # sourcery skip: use-assigned-variable - "Determine the root folder of the device" - try: - c = os.getcwd() - except (OSError, AttributeError): - # unix port - c = "." - r = c - for r in [c, "/sd", "/flash", "/", "."]: - try: - _ = os.stat(r) - break - except OSError: - continue - return r - - -def file_exists(filename: str): - try: - if os.stat(filename)[0] >> 14: - return True - return False - except OSError: - return False - - -def show_help(): - print("-p, --path path to store the stubs in, defaults to '.'") - sys.exit(1) - - -def read_path() -> str: - "get --path from cmdline. [unix/win]" - path = "" - if len(sys.argv) == 3: - cmd = (sys.argv[1]).lower() - if cmd in ("--path", "-p"): - path = sys.argv[2] - else: - show_help() - elif len(sys.argv) == 2: - show_help() - return path - - -def is_micropython() -> bool: - "runtime test to determine full or micropython" - # pylint: disable=unused-variable,eval-used - try: - # either test should fail on micropython - - # b) https://docs.micropython.org/en/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented - # Micropython: NotImplementedError - b = bytes("abc", encoding="utf8") # type: ignore # lgtm [py/unused-local-variable] - - # c) https://docs.micropython.org/en/latest/genrst/core_language.html#function-objects-do-not-have-the-module-attribute - # Micropython: AttributeError - c = is_micropython.__module__ # type: ignore # lgtm [py/unused-local-variable] - return False - except (NotImplementedError, AttributeError): - return True - - -SKIP_FILE = "modulelist.done" - - -def get_modules(skip=0): - # new - for p in LIBS: - fname = p + "/modulelist.txt" - if not file_exists(fname): - continue - try: - with open(fname) as f: - i = 0 - while True: - line = f.readline().strip() - if not line: - break - if len(line) > 0 and line[0] == "#": - continue - i += 1 - if i < skip: - continue - yield line - break - except OSError: - pass - - -def write_skip(done): - # write count of modules already processed to file - with open(SKIP_FILE, "w") as f: - f.write(str(done) + "\n") - - -def read_skip(): - # read count of modules already processed from file - done = 0 - try: - with open(SKIP_FILE) as f: - done = int(f.readline().strip()) - except OSError: - pass - return done - - + def __init__(B,path=E,firmware_id=E): + C=firmware_id + try: + if os.uname().release=='1.13.0'and os.uname().version<'v1.13-103':raise o('MicroPython 1.13.0 cannot be stubbed') + except I:pass + B.info=_info();A.info('Port: {}'.format(B.info[K]));A.info('Board: {}'.format(B.info[U]));F.collect() + if C:B._fwid=C.lower() + elif B.info[Q]==u:B._fwid='{family}-v{version}-{port}-{board}'.format(**B.info).rstrip(V) + else:B._fwid='{family}-v{version}-{port}'.format(**B.info) + B._start_free=F.mem_free() + if path: + if path.endswith(G):path=path[:-1] + else:path=get_root() + B.path='{}/stubs/{}'.format(path,B.flat_fwid).replace('//',G) + try:X(path+G) + except D:A.error('error creating stub folder {}'.format(path)) + B.problematic=['upip','upysh','webrepl_setup','http_client','http_client_ssl','http_server','http_server_ssl'];B.excluded=['webrepl','_webrepl','port_diag','example_sub_led.py','example_pub_button.py'];B.modules=[];B._json_name=E;B._json_first=H + def get_obj_attributes(L,item_instance): + H=item_instance;C=[];K=[] + for A in N(H): + if A.startswith('__')and not A in L.modules:continue + try: + D=getattr(H,A) + try:E=b(type(D)).split("'")[1] + except T:E=B + if E in{v,w,x,y,e,f,g}:G=1 + elif E in{z,A0}:G=2 + elif E in'class':G=3 + else:G=4 + C.append((A,b(D),b(type(D)),D,G)) + except I as J:K.append("Couldn't get attribute '{}' from object '{}', Err: {}".format(A,H,J)) + except p as J:S('MemoryError: {}'.format(J));sleep(1);reset() + C=q([A for A in C if not A[0].startswith('__')],key=lambda x:x[4]);F.collect();return C,K + def add_modules(A,modules):A.modules=q(set(A.modules)|set(modules)) + def create_all_stubs(B): + A.info('Start micropython-stubber {} on {}'.format(__version__,B._fwid));B.report_start();F.collect() + for C in B.modules:B.create_one_stub(C) + B.report_end();A.info('Finally done') + def create_one_stub(C,module_name): + B=module_name + if B in C.problematic:A.warning('Skip module: {:<25} : Known problematic'.format(B));return H + if B in C.excluded:A.warning('Skip module: {:<25} : Excluded'.format(B));return H + I='{}/{}.pyi'.format(C.path,B.replace(J,G));F.collect();E=H + try:E=C.create_module_stub(B,I) + except D:return H + F.collect();return E + def create_module_stub(K,module_name,file_name=E): + I=file_name;C=module_name + if I is E:L=C.replace(J,'_')+'.pyi';I=K.path+G+L + else:L=I.split(G)[-1] + if G in C:C=C.replace(G,J) + N=E + try:N=__import__(C,E,E,'*');Q=F.mem_free();A.info('Stub module: {:<25} to file: {:<70} mem:{:>5}'.format(C,L,Q)) + except O:return H + X(I) + with M(I,h)as P:S=c(K.info).replace('OrderedDict(',B).replace('})','}');T='"""\nModule: \'{0}\' on {1}\n"""\n# MCU: {2}\n# Stubber: {3}\n'.format(C,K._fwid,S,__version__);P.write(T);P.write('from __future__ import annotations\nfrom typing import Any, Generator\nfrom _typeshed import Incomplete\n\n');K.write_object_stub(P,N,C,B) + K.report_add(C,I) + if C not in{'os','sys','logging','gc'}: + try:del N + except(D,r):A.warning('could not del new_module') + F.collect();return R + def write_object_stub(L,fp,object_expr,obj_name,indent,in_class=0): + Z=' at ...>';Y='generator';X='{0}{1}: {3} = {2}\n';W='bound_method';V='Incomplete';O=in_class;N='Exception';M=object_expr;K=' at ';J=fp;D=indent;F.collect() + if M in L.problematic:A.warning('SKIPPING problematic module:{}'.format(M));return + a,Q=L.get_obj_attributes(M) + if Q:A.error(Q) + for(E,H,I,b,d)in a: + if E in['classmethod','staticmethod','BaseException',N]:continue + if E[0].isdigit():A.warning('NameError: invalid name {}'.format(E));continue + if I==""and P(D)<=A4*4: + R=B;S=E.endswith(N)or E.endswith('Error')or E in['KeyboardInterrupt','StopIteration','SystemExit'] + if S:R=N + C='\n{}class {}({}):\n'.format(D,E,R) + if S:C+=D+' ...\n';J.write(C);continue + J.write(C);L.write_object_stub(J,b,'{0}.{1}'.format(obj_name,E),D+' ',O+1);C=D+' def __init__(self, *argv, **kwargs) -> None:\n';C+=D+' ...\n\n';J.write(C) + elif any(A in I for A in[A0,z,'closure']): + T=V;U=B + if O>0:U='self, ' + if W in I or W in H:C='{}@classmethod\n'.format(D)+'{}def {}(cls, *args, **kwargs) -> {}:\n'.format(D,E,T) + else:C='{}def {}({}*args, **kwargs) -> {}:\n'.format(D,E,U,T) + C+=D+' ...\n\n';J.write(C) + elif I=="":0 + elif I.startswith("='1.10.0'and A[C]<='1.19.9':A[C]=A[C][:-2] + if F in A and A[F]: + G=int(A[F]);L=[E,'x86','x64','armv6','armv6m','armv7m','armv7em','armv7emsp','armv7emdp','xtensa','xtensawin'][G>>10] + if L:A[R]=L + A[F]='v{}.{}'.format(G&255,G>>8&3) + if A[D]and not A[C].endswith(W):A[C]=A[C]+W + A[M]=f"{A[C]}-{A[D]}"if A[D]else f"{A[C]}";return A +def A6(version): + A=version;B=J.join([c(A)for A in A[:3]]) + if P(A)>3 and A[3]:B+=V+A[3] + return B +def A7(): + try:from boardname import BOARDNAME as C;A.info('Found BOARDNAME: {}'.format(C)) + except O:A.warning('BOARDNAME not found');C=B + return C +def get_root(): + try:A=os.getcwd() + except(D,I):A=J + B=A + for B in[A,'/sd','/flash',G,J]: + try:C=os.stat(B);break + except D:continue + return B +def Z(filename): + try: + if os.stat(filename)[0]>>14:return R + return H + except D:return H +def m():S("-p, --path path to store the stubs in, defaults to '.'");sys.exit(1) +def read_path(): + path=B + if P(sys.argv)==3: + A=sys.argv[1].lower() + if A in('--path','-p'):path=sys.argv[2] + else:m() + elif P(sys.argv)==2:m() + return path +def n(): + try:A=bytes('abc',encoding='utf8');B=n.__module__;return H + except(o,I):return R +a='modulelist.done' +def A8(skip=0): + for E in A5: + B=E+'/modulelist.txt' + if not Z(B):continue + try: + with M(B)as F: + C=0 + while R: + A=F.readline().strip() + if not A:break + if P(A)>0 and A[0]=='#':continue + C+=1 + if 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 Hk4XOmTEe%C literal 9237 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 C dict[str, str] if ( info["version"] and info["version"].endswith(".0") - and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.23.0 do not have a micro .0 + and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.23.1 do not have a micro .0 and info["version"] <= "1.19.9" ): - # versions from 1.10.0 to 1.23.0 do not have a micro .0 + # versions from 1.10.0 to 1.23.1 do not have a micro .0 info["version"] = info["version"][:-2] # spell-checker: disable diff --git a/src/stubber/board/createstubs_mem_min.py b/src/stubber/board/createstubs_mem_min.py index ebe043c3..76003e46 100644 --- a/src/stubber/board/createstubs_mem_min.py +++ b/src/stubber/board/createstubs_mem_min.py @@ -1,766 +1,307 @@ -"""Create stubs for (all) modules on a MicroPython board. - - This variant of the createstubs.py script is optimised for use on low-memory devices, and reads the list of modules from a text file - `modulelist.txt` in the root or `libs` folder that should be uploaded to the device. - If that cannot be found then only a single module (micropython) is stubbed. - In order to run this on low-memory devices two additional steps are recommended: - - minifification, using python-minifier - to reduce overall size, and remove logging overhead. - - cross compilation, using mpy-cross, - to avoid the compilation step on the micropython device - -This variant was generated from createstubs.py by micropython-stubber v1.23.0 -""" - -# Copyright (c) 2019-2024 Jos Verlinde - -import gc -import os -import sys +x='No report file' +w='Failed to create the report.' +v='{}/{}' +u='method' +t='function' +s='bool' +r='str' +q='float' +p='int' +o='stubber' +n=Exception +m=KeyError +l=sorted +k=NotImplementedError +f=',\n' +e='dict' +d='list' +c='tuple' +b='micropython' +a=TypeError +Z=repr +W='-preview' +V='-' +U='board' +T=IndexError +S=print +R=True +Q='family' +P=len +O=open +N=ImportError +M=dir +K='port' +J='.' +I=AttributeError +H=False +G='/' +F=OSError +E=None +C='version' +B='' +import gc as D,os,sys from time import sleep - -try: - from ujson import dumps -except: - from json import dumps - -try: - from machine import reset # type: ignore -except ImportError: - pass - -try: - from collections import OrderedDict -except ImportError: - from ucollections import OrderedDict # type: ignore - -__version__ = "v1.23.0" -ENOENT = 2 -_MAX_CLASS_LEVEL = 2 # Max class nesting -LIBS = ["lib", "/lib", "/sd/lib", "/flash/lib", "."] - - -# our own logging module to avoid dependency on and interfering with logging module -class logging: - # DEBUG = 10 - INFO = 20 - WARNING = 30 - ERROR = 40 - level = INFO - prnt = print - - @staticmethod - def getLogger(name): - return logging() - - @classmethod - def basicConfig(cls, level): - cls.level = level - - # def debug(self, msg): - # if self.level <= logging.DEBUG: - # self.prnt("DEBUG :", msg) - - def info(self, msg): - if self.level <= logging.INFO: - self.prnt("INFO :", msg) - - def warning(self, msg): - if self.level <= logging.WARNING: - self.prnt("WARN :", msg) - - def error(self, msg): - if self.level <= logging.ERROR: - self.prnt("ERROR :", msg) - - -log = logging.getLogger("stubber") -logging.basicConfig(level=logging.INFO) -# logging.basicConfig(level=logging.DEBUG) - - +try:from ujson import dumps +except:from json import dumps +try:from machine import reset +except N:pass +try:from collections import OrderedDict as g +except N:from ucollections import OrderedDict as g +__version__='v1.23.1' +y=2 +z=2 +A0=['lib','/lib','/sd/lib','/flash/lib',J] +class L: + INFO=20;WARNING=30;ERROR=40;level=INFO;prnt=S + @staticmethod + def getLogger(name):return L() + @classmethod + def basicConfig(A,level):A.level=level + def info(A,msg): + if A.level<=L.INFO:A.prnt('INFO :',msg) + def warning(A,msg): + if A.level<=L.WARNING:A.prnt('WARN :',msg) + def error(A,msg): + if A.level<=L.ERROR:A.prnt('ERROR :',msg) +A=L.getLogger(o) +L.basicConfig(level=L.INFO) class Stubber: - "Generate stubs for modules in firmware" - - def __init__(self, path: str = None, firmware_id: str = None): # type: ignore - try: - if os.uname().release == "1.13.0" and os.uname().version < "v1.13-103": # type: ignore - raise NotImplementedError("MicroPython 1.13.0 cannot be stubbed") - except AttributeError: - pass # Allow testing on CPython 3.11 - self.info = _info() - log.info("Port: {}".format(self.info["port"])) - log.info("Board: {}".format(self.info["board"])) - gc.collect() - if firmware_id: - self._fwid = firmware_id.lower() - else: - if self.info["family"] == "micropython": - self._fwid = "{family}-v{version}-{port}-{board}".format(**self.info).rstrip("-") - else: - self._fwid = "{family}-v{version}-{port}".format(**self.info) - self._start_free = gc.mem_free() # type: ignore - - if path: - if path.endswith("/"): - path = path[:-1] - else: - path = get_root() - - self.path = "{}/stubs/{}".format(path, self.flat_fwid).replace("//", "/") - # log.debug(self.path) - try: - ensure_folder(path + "/") - except OSError: - log.error("error creating stub folder {}".format(path)) - self.problematic = [ - "upip", - "upysh", - "webrepl_setup", - "http_client", - "http_client_ssl", - "http_server", - "http_server_ssl", - ] - self.excluded = [ - "webrepl", - "_webrepl", - "port_diag", - "example_sub_led.py", - "example_pub_button.py", - ] - # there is no option to discover modules from micropython, list is read from an external file. - self.modules = [] # type: list[str] - self._json_name = None - self._json_first = False - - def get_obj_attributes(self, item_instance: object): - "extract information of the objects members and attributes" - # name_, repr_(value), type as text, item_instance - _result = [] - _errors = [] - # log.debug("get attributes {} {}".format(repr(item_instance), item_instance)) - for name in dir(item_instance): - if name.startswith("__") and not name in self.modules: - continue - # log.debug("get attribute {}".format(name)) - try: - val = getattr(item_instance, name) - # name , item_repr(value) , type as text, item_instance, order - # log.debug("attribute {}:{}".format(name, val)) - try: - type_text = repr(type(val)).split("'")[1] - except IndexError: - type_text = "" - if type_text in {"int", "float", "str", "bool", "tuple", "list", "dict"}: - order = 1 - elif type_text in {"function", "method"}: - order = 2 - elif type_text in ("class"): - order = 3 - else: - order = 4 - _result.append((name, repr(val), repr(type(val)), val, order)) - except AttributeError as e: - _errors.append("Couldn't get attribute '{}' from object '{}', Err: {}".format(name, item_instance, e)) - except MemoryError as e: - print("MemoryError: {}".format(e)) - sleep(1) - reset() - - # remove internal __ - # _result = sorted([i for i in _result if not (i[0].startswith("_"))], key=lambda x: x[4]) - _result = sorted([i for i in _result if not (i[0].startswith("__"))], key=lambda x: x[4]) - gc.collect() - return _result, _errors - - def add_modules(self, modules): - "Add additional modules to be exported" - self.modules = sorted(set(self.modules) | set(modules)) - - def create_all_stubs(self): - "Create stubs for all configured modules" - log.info("Start micropython-stubber {} on {}".format(__version__, self._fwid)) - self.report_start() - gc.collect() - for module_name in self.modules: - self.create_one_stub(module_name) - self.report_end() - log.info("Finally done") - - def create_one_stub(self, module_name: str): - if module_name in self.problematic: - log.warning("Skip module: {:<25} : Known problematic".format(module_name)) - return False - if module_name in self.excluded: - log.warning("Skip module: {:<25} : Excluded".format(module_name)) - return False - - file_name = "{}/{}.pyi".format(self.path, module_name.replace(".", "/")) - gc.collect() - result = False - try: - result = self.create_module_stub(module_name, file_name) - except OSError: - return False - gc.collect() - return result - - def create_module_stub(self, module_name: str, file_name: str = None) -> bool: # type: ignore - """Create a Stub of a single python module - - Args: - - module_name (str): name of the module to document. This module will be imported. - - file_name (Optional[str]): the 'path/filename.pyi' to write to. If omitted will be created based on the module name. - """ - if file_name is None: - fname = module_name.replace(".", "_") + ".pyi" - file_name = self.path + "/" + fname - else: - fname = file_name.split("/")[-1] - - if "/" in module_name: - # for nested modules - module_name = module_name.replace("/", ".") - - # import the module (as new_module) to examine it - new_module = None - try: - new_module = __import__(module_name, None, None, ("*")) - m1 = gc.mem_free() # type: ignore - log.info("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1)) - - except ImportError: - # log.debug("Skip module: {:<25} {:<79}".format(module_name, "Module not found.")) - return False - - # Start a new file - ensure_folder(file_name) - with open(file_name, "w") as fp: - info_ = str(self.info).replace("OrderedDict(", "").replace("})", "}") - s = '"""\nModule: \'{0}\' on {1}\n"""\n# MCU: {2}\n# Stubber: {3}\n'.format(module_name, self._fwid, info_, __version__) - fp.write(s) - fp.write("from __future__ import annotations\nfrom typing import Any, Generator\nfrom _typeshed import Incomplete\n\n") - self.write_object_stub(fp, new_module, module_name, "") - - self.report_add(module_name, file_name) - - if module_name not in {"os", "sys", "logging", "gc"}: - # try to unload the module unless we use it - try: - del new_module - except (OSError, KeyError): # lgtm [py/unreachable-statement] - log.warning("could not del new_module") - # do not try to delete from sys.modules - most times it does not work anyway - gc.collect() - return True - - def write_object_stub(self, fp, object_expr: object, obj_name: str, indent: str, in_class: int = 0): - "Write a module/object stub to an open file. Can be called recursive." - gc.collect() - if object_expr in self.problematic: - log.warning("SKIPPING problematic module:{}".format(object_expr)) - return - - # # log.debug("DUMP : {}".format(object_expr)) - items, errors = self.get_obj_attributes(object_expr) - - if errors: - log.error(errors) - - for item_name, item_repr, item_type_txt, item_instance, _ in items: - # name_, repr_(value), type as text, item_instance, order - if item_name in ["classmethod", "staticmethod", "BaseException", "Exception"]: - # do not create stubs for these primitives - continue - if item_name[0].isdigit(): - log.warning("NameError: invalid name {}".format(item_name)) - continue - # Class expansion only on first 3 levels (bit of a hack) - if ( - item_type_txt == "" - and len(indent) <= _MAX_CLASS_LEVEL * 4 - # and not obj_name.endswith(".Pin") - # avoid expansion of Pin.cpu / Pin.board to avoid crashes on most platforms - ): - # log.debug("{0}class {1}:".format(indent, item_name)) - superclass = "" - is_exception = ( - item_name.endswith("Exception") - or item_name.endswith("Error") - or item_name - in [ - "KeyboardInterrupt", - "StopIteration", - "SystemExit", - ] - ) - if is_exception: - superclass = "Exception" - s = "\n{}class {}({}):\n".format(indent, item_name, superclass) - # s += indent + " ''\n" - if is_exception: - s += indent + " ...\n" - fp.write(s) - continue - # write classdef - fp.write(s) - # first write the class literals and methods - # log.debug("# recursion over class {0}".format(item_name)) - self.write_object_stub( - fp, - item_instance, - "{0}.{1}".format(obj_name, item_name), - indent + " ", - in_class + 1, - ) - # end with the __init__ method to make sure that the literals are defined - # Add __init__ - s = indent + " def __init__(self, *argv, **kwargs) -> None:\n" - s += indent + " ...\n\n" - fp.write(s) - elif any(word in item_type_txt for word in ["method", "function", "closure"]): - # log.debug("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name)) - # module Function or class method - # will accept any number of params - # return type Any/Incomplete - ret = "Incomplete" - first = "" - # Self parameter only on class methods/functions - if in_class > 0: - first = "self, " - # class method - add function decoration - if "bound_method" in item_type_txt or "bound_method" in item_repr: - s = "{}@classmethod\n".format(indent) + "{}def {}(cls, *args, **kwargs) -> {}:\n".format(indent, item_name, ret) - else: - s = "{}def {}({}*args, **kwargs) -> {}:\n".format(indent, item_name, first, ret) - s += indent + " ...\n\n" - fp.write(s) - # log.debug("\n" + s) - elif item_type_txt == "": - # Skip imported modules - # fp.write("# import {}\n".format(item_name)) - pass - - elif item_type_txt.startswith("" - if " at " in item_repr: - item_repr = item_repr.split(" at ")[0] + " at ...>" - s = "{0}{1}: {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr) - fp.write(s) - # log.debug("\n" + s) - else: - # keep only the name - # log.debug("# all other, type = '{0}'".format(item_type_txt)) - fp.write("# all other, type = '{0}'\n".format(item_type_txt)) - - fp.write(indent + item_name + " # type: Incomplete\n") - - # del items - # del errors - # try: - # del item_name, item_repr, item_type_txt, item_instance # type: ignore - # except (OSError, KeyError, NameError): - # pass - - @property - def flat_fwid(self): - "Turn _fwid from 'v1.2.3' into '1_2_3' to be used in filename" - s = self._fwid - # path name restrictions - chars = " .()/\\:$" - for c in chars: - s = s.replace(c, "_") - return s - - def clean(self, path: str = None): # type: ignore - "Remove all files from the stub folder" - if path is None: - path = self.path - log.info("Clean/remove files in folder: {}".format(path)) - try: - os.stat(path) # TEMP workaround mpremote listdir bug - - items = os.listdir(path) - except (OSError, AttributeError): - # os.listdir fails on unix - return - for fn in items: - item = "{}/{}".format(path, fn) - try: - os.remove(item) - except OSError: - try: # folder - self.clean(item) - os.rmdir(item) - except OSError: - pass - - def report_start(self, filename: str = "modules.json"): - """Start a report of the modules that have been stubbed - "create json with list of exported modules""" - self._json_name = "{}/{}".format(self.path, filename) - self._json_first = True - ensure_folder(self._json_name) - log.info("Report file: {}".format(self._json_name)) - gc.collect() - try: - # write json by node to reduce memory requirements - with open(self._json_name, "w") as f: - f.write("{") - f.write(dumps({"firmware": self.info})[1:-1]) - f.write(",\n") - f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1]) - f.write(",\n") - f.write('"modules" :[\n') - - except OSError as e: - log.error("Failed to create the report.") - self._json_name = None - raise e - - def report_add(self, module_name: str, stub_file: str): - "Add a module to the report" - # write json by node to reduce memory requirements - if not self._json_name: - raise Exception("No report file") - try: - with open(self._json_name, "a") as f: - if not self._json_first: - f.write(",\n") - else: - self._json_first = False - line = '{{"module": "{}", "file": "{}"}}'.format(module_name, stub_file.replace("\\", "/")) - f.write(line) - - except OSError: - log.error("Failed to create the report.") - - def report_end(self): - if not self._json_name: - raise Exception("No report file") - with open(self._json_name, "a") as f: - f.write("\n]}") - # is used as sucess indicator - log.info("Path: {}".format(self.path)) - - -def ensure_folder(path: str): - "Create nested folders if needed" - i = start = 0 - while i != -1: - i = path.find("/", start) - if i != -1: - p = path[0] if i == 0 else path[:i] - # p = partial folder - try: - _ = os.stat(p) - except OSError as e: - # folder does not exist - if e.args[0] == ENOENT: - try: - os.mkdir(p) - except OSError as e2: - log.error("failed to create folder {}".format(p)) - raise e2 - # next level deep - start = i + 1 - - -def _build(s): - # extract build from sys.version or os.uname().version if available - # sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f' - # sys.implementation.version: 'v1.13-103-gb137d064e' - if not s: - return "" - s = s.split(" on ", 1)[0] if " on " in s else s - if s.startswith("v"): - if not "-" in s: - return "" - b = s.split("-")[1] - return b - if not "-preview" in s: - return "" - b = s.split("-preview")[1].split(".")[1] - return b - - -def _info(): # type:() -> dict[str, str] - try: - fam = sys.implementation[0] # type: ignore - except TypeError: - # testing on CPython 3.11 - fam = sys.implementation.name - - info = OrderedDict( - { - "family": fam, - "version": "", - "build": "", - "ver": "", - "port": sys.platform, # port: esp32 / win32 / linux / stm32 - "board": "UNKNOWN", - "cpu": "", - "mpy": "", - "arch": "", - } - ) - # change port names to be consistent with the repo - if info["port"].startswith("pyb"): - info["port"] = "stm32" - elif info["port"] == "win32": - info["port"] = "windows" - elif info["port"] == "linux": - info["port"] = "unix" - try: - info["version"] = version_str(sys.implementation.version) # type: ignore - except AttributeError: - pass - try: - _machine = sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore - # info["board"] = "with".join(_machine.split("with")[:-1]).strip() - info["board"] = _machine - info["cpu"] = _machine.split("with")[-1].strip() - info["mpy"] = ( - sys.implementation._mpy # type: ignore - if "_mpy" in dir(sys.implementation) - else sys.implementation.mpy if "mpy" in dir(sys.implementation) else "" # type: ignore - ) - except (AttributeError, IndexError): - pass - info["board"] = get_boardname() - - try: - if "uname" in dir(os): # old - # extract build from uname().version if available - info["build"] = _build(os.uname()[3]) # type: ignore - if not info["build"]: - # extract build from uname().release if available - info["build"] = _build(os.uname()[2]) # type: ignore - elif "version" in dir(sys): # new - # extract build from sys.version if available - info["build"] = _build(sys.version) - except (AttributeError, IndexError, TypeError): - pass - # avoid build hashes - # if info["build"] and len(info["build"]) > 5: - # info["build"] = "" - - if info["version"] == "" and sys.platform not in ("unix", "win32"): - try: - u = os.uname() # type: ignore - info["version"] = u.release - except (IndexError, AttributeError, TypeError): - pass - # detect families - for fam_name, mod_name, mod_thing in [ - ("pycopy", "pycopy", "const"), - ("pycom", "pycom", "FAT"), - ("ev3-pybricks", "pybricks.hubs", "EV3Brick"), - ]: - try: - _t = __import__(mod_name, None, None, (mod_thing)) - info["family"] = fam_name - del _t - break - except (ImportError, KeyError): - pass - - if info["family"] == "ev3-pybricks": - info["release"] = "2.0.0" - - if info["family"] == "micropython": - info["version"] - if ( - info["version"] - and info["version"].endswith(".0") - and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.23.0 do not have a micro .0 - and info["version"] <= "1.19.9" - ): - # versions from 1.10.0 to 1.23.0 do not have a micro .0 - info["version"] = info["version"][:-2] - - # spell-checker: disable - if "mpy" in info and info["mpy"]: # mpy on some v1.11+ builds - sys_mpy = int(info["mpy"]) - # .mpy architecture - arch = [ - None, - "x86", - "x64", - "armv6", - "armv6m", - "armv7m", - "armv7em", - "armv7emsp", - "armv7emdp", - "xtensa", - "xtensawin", - ][sys_mpy >> 10] - if arch: - info["arch"] = arch - # .mpy version.minor - info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3) - if info["build"] and not info["version"].endswith("-preview"): - info["version"] = info["version"] + "-preview" - # simple to use version[-build] string - info["ver"] = f"{info['version']}-{info['build']}" if info["build"] else f"{info['version']}" - - return info - - -def version_str(version: tuple): # -> str: - v_str = ".".join([str(n) for n in version[:3]]) - if len(version) > 3 and version[3]: - v_str += "-" + version[3] - return v_str - - -def get_boardname() -> str: - "Read the board name from the boardname.py file that may have been created upfront" - try: - from boardname import BOARDNAME # type: ignore - - log.info("Found BOARDNAME: {}".format(BOARDNAME)) - except ImportError: - log.warning("BOARDNAME not found") - BOARDNAME = "" - return BOARDNAME - - -def get_root() -> str: # sourcery skip: use-assigned-variable - "Determine the root folder of the device" - try: - c = os.getcwd() - except (OSError, AttributeError): - # unix port - c = "." - r = c - for r in [c, "/sd", "/flash", "/", "."]: - try: - _ = os.stat(r) - break - except OSError: - continue - return r - - -def file_exists(filename: str): - try: - if os.stat(filename)[0] >> 14: - return True - return False - except OSError: - return False - - -def show_help(): - print("-p, --path path to store the stubs in, defaults to '.'") - sys.exit(1) - - -def read_path() -> str: - "get --path from cmdline. [unix/win]" - path = "" - if len(sys.argv) == 3: - cmd = (sys.argv[1]).lower() - if cmd in ("--path", "-p"): - path = sys.argv[2] - else: - show_help() - elif len(sys.argv) == 2: - show_help() - return path - - -def is_micropython() -> bool: - "runtime test to determine full or micropython" - # pylint: disable=unused-variable,eval-used - try: - # either test should fail on micropython - - # b) https://docs.micropython.org/en/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented - # Micropython: NotImplementedError - b = bytes("abc", encoding="utf8") # type: ignore # lgtm [py/unused-local-variable] - - # c) https://docs.micropython.org/en/latest/genrst/core_language.html#function-objects-do-not-have-the-module-attribute - # Micropython: AttributeError - c = is_micropython.__module__ # type: ignore # lgtm [py/unused-local-variable] - return False - except (NotImplementedError, AttributeError): - return True - - + def __init__(B,path=E,firmware_id=E): + C=firmware_id + try: + if os.uname().release=='1.13.0'and os.uname().version<'v1.13-103':raise k('MicroPython 1.13.0 cannot be stubbed') + except I:pass + B.info=_info();A.info('Port: {}'.format(B.info[K]));A.info('Board: {}'.format(B.info[U]));D.collect() + if C:B._fwid=C.lower() + elif B.info[Q]==b:B._fwid='{family}-v{version}-{port}-{board}'.format(**B.info).rstrip(V) + else:B._fwid='{family}-v{version}-{port}'.format(**B.info) + B._start_free=D.mem_free() + if path: + if path.endswith(G):path=path[:-1] + else:path=get_root() + B.path='{}/stubs/{}'.format(path,B.flat_fwid).replace('//',G) + try:X(path+G) + except F:A.error('error creating stub folder {}'.format(path)) + B.problematic=['upip','upysh','webrepl_setup','http_client','http_client_ssl','http_server','http_server_ssl'];B.excluded=['webrepl','_webrepl','port_diag','example_sub_led.py','example_pub_button.py'];B.modules=[];B._json_name=E;B._json_first=H + def get_obj_attributes(L,item_instance): + H=item_instance;C=[];K=[] + for A in M(H): + if A.startswith('__')and not A in L.modules:continue + try: + E=getattr(H,A) + try:F=Z(type(E)).split("'")[1] + except T:F=B + if F in{p,q,r,s,c,d,e}:G=1 + elif F in{t,u}:G=2 + elif F in'class':G=3 + else:G=4 + C.append((A,Z(E),Z(type(E)),E,G)) + except I as J:K.append("Couldn't get attribute '{}' from object '{}', Err: {}".format(A,H,J)) + except MemoryError as J:S('MemoryError: {}'.format(J));sleep(1);reset() + C=l([A for A in C if not A[0].startswith('__')],key=lambda x:x[4]);D.collect();return C,K + def add_modules(A,modules):A.modules=l(set(A.modules)|set(modules)) + def create_all_stubs(B): + A.info('Start micropython-stubber {} on {}'.format(__version__,B._fwid));B.report_start();D.collect() + for C in B.modules:B.create_one_stub(C) + B.report_end();A.info('Finally done') + def create_one_stub(C,module_name): + B=module_name + if B in C.problematic:A.warning('Skip module: {:<25} : Known problematic'.format(B));return H + if B in C.excluded:A.warning('Skip module: {:<25} : Excluded'.format(B));return H + I='{}/{}.pyi'.format(C.path,B.replace(J,G));D.collect();E=H + try:E=C.create_module_stub(B,I) + except F:return H + D.collect();return E + def create_module_stub(K,module_name,file_name=E): + I=file_name;C=module_name + if I is E:L=C.replace(J,'_')+'.pyi';I=K.path+G+L + else:L=I.split(G)[-1] + if G in C:C=C.replace(G,J) + M=E + try:M=__import__(C,E,E,'*');Q=D.mem_free();A.info('Stub module: {:<25} to file: {:<70} mem:{:>5}'.format(C,L,Q)) + except N:return H + X(I) + with O(I,'w')as P:S=str(K.info).replace('OrderedDict(',B).replace('})','}');T='"""\nModule: \'{0}\' on {1}\n"""\n# MCU: {2}\n# Stubber: {3}\n'.format(C,K._fwid,S,__version__);P.write(T);P.write('from __future__ import annotations\nfrom typing import Any, Generator\nfrom _typeshed import Incomplete\n\n');K.write_object_stub(P,M,C,B) + K.report_add(C,I) + if C not in{'os','sys','logging','gc'}: + try:del M + except(F,m):A.warning('could not del new_module') + D.collect();return R + def write_object_stub(L,fp,object_expr,obj_name,indent,in_class=0): + Z=' at ...>';Y='generator';X='{0}{1}: {3} = {2}\n';W='bound_method';V='Incomplete';O=in_class;N='Exception';M=object_expr;K=' at ';J=fp;E=indent;D.collect() + if M in L.problematic:A.warning('SKIPPING problematic module:{}'.format(M));return + a,Q=L.get_obj_attributes(M) + if Q:A.error(Q) + for(F,H,I,b,g)in a: + if F in['classmethod','staticmethod','BaseException',N]:continue + if F[0].isdigit():A.warning('NameError: invalid name {}'.format(F));continue + if I==""and P(E)<=z*4: + R=B;S=F.endswith(N)or F.endswith('Error')or F in['KeyboardInterrupt','StopIteration','SystemExit'] + if S:R=N + C='\n{}class {}({}):\n'.format(E,F,R) + if S:C+=E+' ...\n';J.write(C);continue + J.write(C);L.write_object_stub(J,b,'{0}.{1}'.format(obj_name,F),E+' ',O+1);C=E+' def __init__(self, *argv, **kwargs) -> None:\n';C+=E+' ...\n\n';J.write(C) + elif any(A in I for A in[u,t,'closure']): + T=V;U=B + if O>0:U='self, ' + if W in I or W in H:C='{}@classmethod\n'.format(E)+'{}def {}(cls, *args, **kwargs) -> {}:\n'.format(E,F,T) + else:C='{}def {}({}*args, **kwargs) -> {}:\n'.format(E,F,U,T) + C+=E+' ...\n\n';J.write(C) + elif I=="":0 + elif I.startswith("='1.10.0'and A[C]<='1.19.9':A[C]=A[C][:-2] + if F in A and A[F]: + G=int(A[F]);L=[E,'x86','x64','armv6','armv6m','armv7m','armv7em','armv7emsp','armv7emdp','xtensa','xtensawin'][G>>10] + if L:A[R]=L + A[F]='v{}.{}'.format(G&255,G>>8&3) + if A[D]and not A[C].endswith(W):A[C]=A[C]+W + A[O]=f"{A[C]}-{A[D]}"if A[D]else f"{A[C]}";return A +def A1(version): + A=version;B=J.join([str(A)for A in A[:3]]) + if P(A)>3 and A[3]:B+=V+A[3] + return B +def A2(): + try:from boardname import BOARDNAME as C;A.info('Found BOARDNAME: {}'.format(C)) + except N:A.warning('BOARDNAME not found');C=B + return C +def get_root(): + try:A=os.getcwd() + except(F,I):A=J + B=A + for B in[A,'/sd','/flash',G,J]: + try:C=os.stat(B);break + except F:continue + return B +def h(filename): + try: + if os.stat(filename)[0]>>14:return R + return H + except F:return H +def i():S("-p, --path path to store the stubs in, defaults to '.'");sys.exit(1) +def read_path(): + path=B + if P(sys.argv)==3: + A=sys.argv[1].lower() + if A in('--path','-p'):path=sys.argv[2] + else:i() + elif P(sys.argv)==2:i() + return path +def j(): + try:A=bytes('abc',encoding='utf8');B=j.__module__;return H + except(k,I):return R def main(): - stubber = Stubber(path=read_path()) - # stubber = Stubber(path="/sd") - # Option: Specify a firmware name & version - # stubber = Stubber(firmware_id='HoverBot v1.2.1') - stubber.clean() - - # Read stubs from modulelist in the current folder or in /libs - # fall back to default modules - def get_modulelist(stubber): - # new - gc.collect() - stubber.modules = [] # avoid duplicates - for p in LIBS: - fname = p + "/modulelist.txt" - if not file_exists(fname): - continue - with open(fname) as f: - # print("DEBUG: list of modules: " + p + "/modulelist.txt") - while True: - line = f.readline().strip() - if not line: - break - if len(line) > 0 and line[0] != "#": - stubber.modules.append(line) - gc.collect() - print("BREAK") - break - - if not stubber.modules: - stubber.modules = ["micropython"] - # _log.warn("Could not find modulelist.txt, using default modules") - gc.collect() - - stubber.modules = [] # avoid duplicates - get_modulelist(stubber) - - gc.collect() - - stubber.create_all_stubs() - - -if __name__ == "__main__" or is_micropython(): - if not file_exists("no_auto_stubber.txt"): - try: - gc.threshold(4 * 1024) # type: ignore - gc.enable() - except BaseException: - pass - main() + stubber=Stubber(path=read_path());stubber.clean() + def A(stubber): + D.collect();stubber.modules=[] + for C in A0: + B=C+'/modulelist.txt' + if not h(B):continue + with O(B)as E: + while R: + A=E.readline().strip() + if not A:break + if P(A)>0 and A[0]!='#':stubber.modules.append(A) + D.collect();S('BREAK');break + if not stubber.modules:stubber.modules=[b] + D.collect() + stubber.modules=[];A(stubber);D.collect();stubber.create_all_stubs() +if __name__=='__main__'or j(): + if not h('no_auto_stubber.txt'): + try:D.threshold(4*1024);D.enable() + except BaseException:pass + main() \ No newline at end of file diff --git a/src/stubber/board/createstubs_mem_mpy.mpy b/src/stubber/board/createstubs_mem_mpy.mpy index ecca760107385e48f224b1810fa7b580e52c4213..3561c50026b951183a925fd28471d055f74e4655 100644 GIT binary patch literal 9118 zcmaJ_TTmO>nLcVHEX%Td)RNoWjM{1;KoSxNAp^dFXlwz4ZGnZ&%-9;yYCyJbBMAd0 zjCIe&*eYo{Fm?l{?i(-MGoa~xJfF?$+BE3FU*wug=MtQw6YplSw-foT%OOT z5T0GgN#%4thgOw9fhKNH7ze9rURK~^2uTf znP-#PbY>YD3;ALhF@Chya#b#t=zcZ&ISJ|WB~)8lE+JDXBg+M3ET^+FGNl%>1sGen zTmnI(`fV!8CAo|&*`zd|&dG>7Q%uQ4IVGm0GUB9sCL^nQmQdZo%RS50E`PAw--T*3 z=^11U&~Hnilu{e^z-%U2npfM*D~O$w%dh0;=D;v+CRs{LL;2indJdT~@>My5j3cK{ zozkfHh1~EXpcVo{C%i)sW%K?;Se$tf~MlR~_{LJMOe+FkB}*l=OeVQxDwWuFSsk25Pf!&`%~ikUbF%6;WUo%kxfEhmmIA^` zBUe>U+eH(#8r7*i%p%gSwj*X0DI?CsV!A9-jesb=x+5roEE!0A6(@YM%=`WR7ic-(c7W3C2DS!fN z&q6-1r*?G1yuYnI@Or-sY)F^NlyelFshFh=46(>U1^_dyAWY#`UM@ft?A+_ z;0R!@g2#T8E~kbaN6a81*BJqsA``=dW1v?inVm@`!6!$lQ_BHXHFwh&x|8x3^Ai?U&M769? zfGs2D8n8nr3iM!rbU`kbmjR~0yoy{Xz5eo285zvg-0`b5($Fyb^*v^x zI>|*!69%(!_;k+4r{vk>LZ)1zX0`eu^%+C0wh$SZjsh~c#?w+Ue|8ye z8#!L1IbKTU=wUN5Y)S7KaH4ZXyGg8eTwSS#8VUi8Cp=Wn2MZhix)3t#0m@MIqSqV4 zLA5Gto`$Sf4+86UoVB+~dI`w51yFO(e7RhJNTz8}4f|f|B}%2t_Iyb$(lTA0*LvDK zD=#G}V-$4;Ek$7_ywtspCCE`Grg>*IMR*LxEnZ{K>bjthVw%1X*4K zojTy-fpfHA?HiY~`Qoz5LE4COl9E54r9LjGKJL(5N+CfnBPuXXrgKo8miZKLEi(8g zucQmS1}xa6|7fshl~NS_^z&o6{9=xOxl$Nf{@(?|+m*u5F$vN9`o@3@ihuQ>!=0-< z6t4c2{^LCqurFQl+K}O})9EygtL3#HEbjzgPy@PFO>{!w$A{vuOK=sM^dQI)=ytD~ zkYP^sSt2pJPzK^nBzTPpcok;q?Wtr^MSvVB7^>5Qxn&=}!?T)1g7Rl+9`dv*^kpe9 z!AW%zjhvb)DLKREVUehRsdndy|PO8gkz*k33j1dd~LIp zRl1e1Be=5q*QIs}p9VW>%l;jI1y^}Npe_s1VAg+a974P^*C6l!WIhkfS@iMLD=?)( z1s2+QK^^MnUy2zr@I&4pUBU!wQBROp3NFMLzNWn^uapX-aDRQJ_C1s8*Sjkn6EMgN!mhc{q zEMqfAuHn5LS-}>Le8WmsIdaKL-r~r0ypK~5#+;(VR!(88N)4yzt%`wDY9}~l7iKxd zXjOJ|ipi?%;S{q~*~=*ws{+d~wsCM*wQ~wT6sGo7uoI?)CMT!B+B$9SENnrCUDaXTMcl$E%?hVD;Gf5C z+=|<9yH#PmVC|uEL4-XahVxiGcCVEb!Vm!o+CJQ2_1OGj1x=p|>Tm!=0zroB4%Tq} zK|OaOXy7uzS}qsd#g&3aZZWuaa@xirX7(z zUyAgqA_G+s^Nz?~P2`=Bu`}?#_)Y8ExJBmQHom{_UE%V;`+F`AybVxO?F)c?R;3>P zJj`S_B!ndh;Sj(U(hkOuU0@CI(rMjfH{^xCLL1wYG_85w3vA+#x2?0xHNHgX`$*hw8G^;C+1 z4QirhwtF3VFG9NM(FQQB?zuRjs@^N?gGH((ka?IiVA3dH!78wVO|W}+!RjVV4r0=b zi9^^ga9#rp@m^|>fmQ0^afns+!^0VqzODa=4HChlv{$Cuydj~@;cPw8>2*3h4#Cl~ z{>Y0Sd(jhC!5bl6V(dS4pWL+{!^8!ajNS#k4O>HzNL%!M9jkaY?uMAB(E}{G&MMvy zLV$exQA}FGIuieMbRSrte=eSyW|hWILG2G&l4nVdC0UkaShB*BYb;sr*tI2!OMj3Z_;L%J#{aB*Uc-CVE4t46vnW(gpQ7^p3i>ml8Lgeam6G3o8LL6+|Uo}BFMP)uCVs`LbSVBx$>nf+LoUbR7PrH3w7s*n zZ{zX$liL8@--?$Yrt~aK0$~P|&KI9h1!$h!eepN~iF=f4p}QXc1fF-c#BoiYgC(E= zZ1FVeW0jUW(1JfqxvB0Lq(hV-gH8r^Y{H zYk>^_=%h|)24q*WxEqsDnE70Az)ql^?LI^;tpDPko%DPJyV9q?Ljyb$zV`v>(iqlH z$6mh({lj;_Q`2{FJ%+UI#dRI}t$!8Yi0a#FH4m1cdL5ot{}G3yy;qE-_@%bG-$MKN&oY37J z^a5k_J;kK|Q^ENeCPzNUI|M?v^-ig}}z&1clOhb)e@B4v@ znwSK32+;0i+*UX1V#QG^(d%MCS(i02_ANuF!EJNfTbtec-JHvY$q7sbFd6*VtKSkM zk!F_-GKfUNNSx62cG>$}Hg)reuBBOMc_#idVHJ}LL)Iw#%|HK2{4Vqx-3MGY`)ePE zb;I?vfyodwE%X2plVMCwdTX}ab*?&b=wtT*!3~>Ym(6t`T;r~X!Sk+q7slk&i{H8H ze*__N)q}o~e|c-CO~dsg*M3Z*_P*Po4@1RAUOXT#!iH%V7Fu7xKCp+-_SG%EdxKR% zu4W!VieCm|!(`N3bBPk9_}j`y?gpxPE#(r&cY}> z01GQK?t|Ma54xJ-)y03PxS%RcU^0oxIZWaodJJ1)!qxQQGivsfyGZ~tQRC6n((JN_ z4RGMfNA9K{Qa_Bl?DnHS4C^ZK4_>(K|HqKv|N7#cs6{T(rVz0AwKS^`QfDCWFNDq) zG{S$l7L#=3jYojMdC~O8p7|L@GCa;4GZLtS^I^f8SnUIdqo6q(z zS=w$L^f5k2W8lw*8I}}gHZZyPSEYq+SO!!%>ej5g^kl@YvI|RsFaw^Z>fe!LPulbs z!RnawPUr6#CpiozcPQb?M*(Lb*y`|5g@;C10$0Q|Tppt%J4RdrVn6&C=AA>Ys0OH- zMovM^qAn3a5C>ZDFiFr-!$3%=7*Td9!E_SbGO5~C!?n|Wk0EWGIHduEnJDSisPA>S0xUE z#Pq13Nxb%S#HzYk&;$3WT6`eKp0?GhK8}6TW}uCA^5PL3I`m&}#iYM^qpPl;WGcT9 zT7&K04lh2kxi%yM;YjOiTlAgg=sF2Apw{5p!!@9$Q% zPkWEUDHtB&flWO42uB{{p(j}U1P?#OCqKofKEu(^qr2G#+ejt;#VC88C6}IyqtkT2 zHu?n^$tnkZyFX$T;|v(1VpeIqPum~PfY2DcWz67LScRcOAJDIR^y@Al`o|CGD+t(1 zS$G7WrroJXN*%rP3`WjqBNOTfKKo3J-quD2qE zTkuLmm5oKB5NG<@bm%7^!y0DlWBXA&N$*DXb7A!0gDDK*{@S8=FY&y!GfcYNF1_g^;>;bR196h4oYcneEq(z7Yp??@v9rA0IliJ*H@5kypVC^(eYx z9A+DA)VTXok%3VgppKpc&iB+*DU6y83O7Cziy%678}8n4HJSy_s8;^;2ev8AGjy?@ zu77Fq#s)s`saapg7d$mg&UJ-9oC(@+6fC%OJd7yHhiZu~LKK)}H9;_FpCAcw-bAqqO`M)X2G zlM0;0m+gJ(F!aJqWDH-a_QSddA5J}xZX6vnXSm8=LVatSkaG5H5Us~nDBRWL4?SHN1eKRM9s9FT5r-ZK{Qo*4{@0XK0k6R*KUDZm5*bb?FA` z!HbIfmc`lSi|&W5dqAN|{F{Fgfx~tIA?=s6CDm7hm;|@=+NE0%uv%d^t(Xn=WkM-j zdb_TUy>`p)>hK0yftnj1g2w>CE8w(eVv@Fi%i#Bh&2_=FC63SmCH@e8AXtJI(a5$arIWyk!T%D&fvH3bxaVvn?7V$ZA1rAqUw>w>MermxJ!ascq7AG z2y@F3y0MoHV0dr37J^j>2$<-@XgY{CwOJK6?y9;LFmh@gU>m<_dTZaOz zE$!`2Z;Qj#AK1AGZ2!V=C^0q%#`L^+#4c{Y4{ebM$e5vZYV2_&2gNcKMwEh{+<_@@ zrRh#c2e;pIcPepAA%W3}i=x$b1MoL6E+rBGaq7dmn)XrEzTMuuzw)fQF? z$W%xvN)8!|$+Ut@iN$mdXp2`0*$gtOzoxuWP>QHA9g`Q583oxU@(CrcB*dg#L^e5_ zN-1(NnavbX)8fms#%o<2zV41rRGUi1kZII%NN!VCpIVkcNrymT4P>GDX{{$)Ys7o zP9XE-hylJ0T^(I?PQS-Hj-%S;*8=bp@FP>aK z7}#1_mQ#xfC4sCG#Q;fllT91XCG&+MYN77P#;-`RVlkhLF9N1Y%d$ybx!(l_YAZ@@c1NtY)LSRIgt`hP76tUqj^~ekq?UDpYDftgoI3LLgFK zZVe5Qe$#*o3^VgrF$aG-Iyy|KakUd-x@!#?DGA)4%2IGa)*%QO3JOKQfr65n^Khss zo?Xm4MCaJ70b z^tJ#_LVT;>rD~7LI#WJP=lWHI!7*vb76lnRa2E~6;z~|I`WVt*K((feYY%E!0f;%GL8-frvd)6^T^vnmM{chPRSQnP)uJy>3(uBVwEI- zRO)dFO_Q^rLJH6uFqJS;t(+>LvZXd%n5WsT=_Wz^9|9+)0gxd`GBU7w%S`$8X!&UQ zpsgAK8mg#>pq-XB%;k`+>UgRI!Zdc(#RfoEKy-wY8A*%EC6*dbqWh7488MbjR*Eea zvuemdMd~On7ZKA?4cZR1N-~G$V!)r^irFe8a9ZJ^Ic(B11{Jg!MuT)y4Ffyy;u$%^RoE*#0%i`KWaljacU)3t;TKl!tYFt6( zpkVS-(&$=D_z! z8V1b1mu6C-klI-6$rvVD5hMC%?6-+7%fFTWr>T-W( z#l!7FuErrzyeuq05?7gCfV&B9YLcnv)Kp0*DK4WdRZ$Z$uTGB)O-@3Uf7y6D)}U@U zG!Cd%^<6S^EtX2cK|oS{)VNa@=>5)9h%rO!q?L-*HP`A|YcJg1MO_3L#{pz!k^q=4 zt=zb2KjY!t?yG?P^MzKf{S-G2zNUNj(mnJKtzj_Vx4PD+o~zX+pg307=$UX0pe>w2 zkNDbQt84TqVV7}r?e}x-RyZxfKo?UjQ$w3RrY1>~i_tm*#t_eDA+ze@D@7%i&&MEP z7&-#EZ!_8AiNy}=mN62&l(xPZ%vzrIMUQsCgYAsL) zVj9P@)roorT)MhiJ%R7#_|-Mu!|^ozsU=}Oq)aE$U36Z~w5 zpPjI`v}C!PN82*opS-hfTW8nbTHmwYux?(jOZ2Tb!ra~A;@uIU6gjN7>EXA=Rs+8V zo1ugmx9)IlGh<_-;l>i)12bdu9^0PiFpg@|I&G@ENY_oUUf*o8nWDq>XU*i|Gcgn$ z)|MJzX>W6btsy#mKwFxC6DYM&qv|f=eKvBbY$FoTY$OiDCVbGm-*PBWM$t1q9X<>khkbh65nqkX=`+~8KE`&zS8KcE+hdb` zM%%ov&bHt)*{=BNZE0VFEpH|=ZhjgXG^hPYOq=HgG0^vc2 zCD4JiMBKXRZg7~hL`~W@wxXb;YjrjvfGbER_=14)4S(GCMM-z& zZBi_dYo!_f#FXId>2V4pZPCsVp>=5B96#-wgX+PDlN`Tbw}fyjtxr;ADap2z|U2pULzi_67^Pi zK}YY3BX_N-0PH0-!6Mz{o590R#i+2?>X?Rk=~x+Ai2=6+L!9l=KfI`MQRa=k-8vl$#!_E{a34GY;50B$2Db-<+GXexy3*&`Kv^toRb z9XJ0xMVqtFXUq58T{<$zDtvG1({Bgad61kZ>C) zvKWDpc@loui2Pm8T{cWC5F1OSn|(s4tt%)j@RH+1i>JjY@<)b`gxVc%a9z$ewAF?l zv*c>Uuaic;)IGUxZNtP0stn!Vuq%(qtIk^+^;+g)WBLK z@~O*sakvdQR-cLTr$Yx>q6i1Aj)wsdHp()D{+ei_Yn-}^!9RGLC2#rAUB3>KqmaO5 z-4-}jjgWh8y{s=AhZ@K%ahHAwCYDCn%}QyeYM@Y8#cK_EHjfi06@^?c{oUou_Rf*v!K*y)ajkN)!}EHlt94>%g139d zoOdJtB%ZI{LtFtpCaupO!_|--_nvPBhwnm^L1tZ_d5n)%w#5;Z^(eLm^qY|>cONvp#MbR;2i3e_rW^>!>8|ANe2W}lzs&ej5^Bu-u(*fGejd7F2b~vnkHJo zHQ0dnV|}|}`|rieVS~${D+j`FF^2SGmp|L66?G19SY@YxP<K3AA`+261`y-sh(mA(l}^{TsH2@ApMxU1q$L;<>380-wYf)1Vb_Q$h7Qy~h z{4QZh_C8IA%j>`Wh4>D^uAe>x$Hq^8gdQ zFF2on{*(KH>nj_}Y`M0@!XZF_S5;hky{+aFg*WjZN|>DbTkLY+2c@5j1DJfnfidZ` zTed@|;rnP?JR`J%PywCbrm}qysCoW*a8ziclNS6zzyP;l(*N>xza?M_&r>lJ{5Kd; zCr#X`Gg&-2QSc(&~0_tEovCi>HUY#1^;vEo+qmjSw}8!^> zxQY9A|8!+~dZcdat{rX9M}s{PxjY$#j)D|#f14X#j7?_!v$z-oq3h zHiMlN`IhmP#Z!d7;`VLM-U3+X85QhqewueqR{&2$z>iH01$uTsEo^~yf~VjKUos4X ztzitMVjh|anvNkr(2H=_z++5CD0Bq$PoPW#?RPxB>KnzG+%3K~E?TwK$b&zb2CgqjpsyRf_e=9;l5FN1r=f-m$M<{gsOwHffAv zaz;B)wK^sIQ;Vpdgww^yyxcvp!BD6Xp{9+q_#yed)ySWwo@6v{q!$$$?H!IhF zDgL@tDM6BLlxBj18<7ea|8dv^A;RAa=$;IZYr=k1`e^9H1B+0aVTtrq%&HtMz!BXw z@y9zRhQ1XRVCg+2^0BK{HF4x47efaX@_Y*}4gJ^K5&2uMwQ^3faQi<5!Qbi-+`L2Z zZG=MDCF9nOZE^1LaFh}( zT_1wJY=ft61N-9GMdQ|m{q(0N4h#W&ERJ8p$0?zg{+yscCn2OaEAS8iZC?j%!}t`; zW`l7`!{2xcgg%uJQ3?H&5L5|$Va#4oFYfm1D!6<0E6r<`1mYsSq5m!VjT>i3htf7r+;VD>EJnh;&-Sgf-cPV5{qIu2O^F#NFy$bk8`f8Z4@I{@GyP zum#LbZvvycYpTyGP<#fvAV7gLAk;wrqJUjgB_H3jgjKuHeXt3(zSt)i1V*TBtC2?9 zJ8PbN^l{h<{n>*^5D!&w7_`t{ zW7nu0&`5R9h*#+^vBMv-kuwil3Fhjx*v`i6u5V z^6Ng0;0Zj5BX|l=<1=^$&*HQATwDFaTVKw6_DCFfRKX`_aTJ;so?QRU!*{>D@tc62 zI)fTjJWHZh5`Q&tAGy=J=7Q+YxRqVF~`_<#Mz1VYPl30mxMR7;d5leVL`w)CC9?2GrMIlJJYvHAwi;4IF; zGw?$;ZFyr;6*0ftAxz0o bool: # type: ignore - """Create a Stub of a single python module - - Args: - - module_name (str): name of the module to document. This module will be imported. - - file_name (Optional[str]): the 'path/filename.pyi' to write to. If omitted will be created based on the module name. - """ - if file_name is None: - fname = module_name.replace(".", "_") + ".pyi" - file_name = self.path + "/" + fname - else: - fname = file_name.split("/")[-1] - - if "/" in module_name: - # for nested modules - module_name = module_name.replace("/", ".") - - # import the module (as new_module) to examine it - new_module = None - try: - new_module = __import__(module_name, None, None, ("*")) - m1 = gc.mem_free() # type: ignore - log.info("Stub module: {:<25} to file: {:<70} mem:{:>5}".format(module_name, fname, m1)) - - except ImportError: - # log.debug("Skip module: {:<25} {:<79}".format(module_name, "Module not found.")) - return False - - # Start a new file - ensure_folder(file_name) - with open(file_name, "w") as fp: - info_ = str(self.info).replace("OrderedDict(", "").replace("})", "}") - s = '"""\nModule: \'{0}\' on {1}\n"""\n# MCU: {2}\n# Stubber: {3}\n'.format( - module_name, self._fwid, info_, __version__ - ) - fp.write(s) - fp.write( - "from __future__ import annotations\nfrom typing import Any, Generator\nfrom _typeshed import Incomplete\n\n" - ) - self.write_object_stub(fp, new_module, module_name, "") - - self.report_add(module_name, file_name) - - if module_name not in {"os", "sys", "logging", "gc"}: - # try to unload the module unless we use it - try: - del new_module - except (OSError, KeyError): # lgtm [py/unreachable-statement] - log.warning("could not del new_module") - # do not try to delete from sys.modules - most times it does not work anyway - gc.collect() - return True - - def write_object_stub(self, fp, object_expr: object, obj_name: str, indent: str, in_class: int = 0): - "Write a module/object stub to an open file. Can be called recursive." - gc.collect() - if object_expr in self.problematic: - log.warning("SKIPPING problematic module:{}".format(object_expr)) - return - - # # log.debug("DUMP : {}".format(object_expr)) - items, errors = self.get_obj_attributes(object_expr) - - if errors: - log.error(errors) - - for item_name, item_repr, item_type_txt, item_instance, _ in items: - # name_, repr_(value), type as text, item_instance, order - if item_name in ["classmethod", "staticmethod", "BaseException", "Exception"]: - # do not create stubs for these primitives - continue - if item_name[0].isdigit(): - log.warning("NameError: invalid name {}".format(item_name)) - continue - # Class expansion only on first 3 levels (bit of a hack) - if ( - item_type_txt == "" - and len(indent) <= _MAX_CLASS_LEVEL * 4 - # and not obj_name.endswith(".Pin") - # avoid expansion of Pin.cpu / Pin.board to avoid crashes on most platforms - ): - # log.debug("{0}class {1}:".format(indent, item_name)) - superclass = "" - is_exception = ( - item_name.endswith("Exception") - or item_name.endswith("Error") - or item_name - in [ - "KeyboardInterrupt", - "StopIteration", - "SystemExit", - ] - ) - if is_exception: - superclass = "Exception" - s = "\n{}class {}({}):\n".format(indent, item_name, superclass) - # s += indent + " ''\n" - if is_exception: - s += indent + " ...\n" - fp.write(s) - continue - # write classdef - fp.write(s) - # first write the class literals and methods - # log.debug("# recursion over class {0}".format(item_name)) - self.write_object_stub( - fp, - item_instance, - "{0}.{1}".format(obj_name, item_name), - indent + " ", - in_class + 1, - ) - # end with the __init__ method to make sure that the literals are defined - # Add __init__ - s = indent + " def __init__(self, *argv, **kwargs) -> None:\n" - s += indent + " ...\n\n" - fp.write(s) - elif any(word in item_type_txt for word in ["method", "function", "closure"]): - # log.debug("# def {1} function/method/closure, type = '{0}'".format(item_type_txt, item_name)) - # module Function or class method - # will accept any number of params - # return type Any/Incomplete - ret = "Incomplete" - first = "" - # Self parameter only on class methods/functions - if in_class > 0: - first = "self, " - # class method - add function decoration - if "bound_method" in item_type_txt or "bound_method" in item_repr: - s = "{}@classmethod\n".format(indent) + "{}def {}(cls, *args, **kwargs) -> {}:\n".format( - indent, item_name, ret - ) - else: - s = "{}def {}({}*args, **kwargs) -> {}:\n".format(indent, item_name, first, ret) - s += indent + " ...\n\n" - fp.write(s) - # log.debug("\n" + s) - elif item_type_txt == "": - # Skip imported modules - # fp.write("# import {}\n".format(item_name)) - pass - - elif item_type_txt.startswith("" - if " at " in item_repr: - item_repr = item_repr.split(" at ")[0] + " at ...>" - s = "{0}{1}: {2} ## {3} = {4}\n".format(indent, item_name, t, item_type_txt, item_repr) - fp.write(s) - # log.debug("\n" + s) - else: - # keep only the name - # log.debug("# all other, type = '{0}'".format(item_type_txt)) - fp.write("# all other, type = '{0}'\n".format(item_type_txt)) - - fp.write(indent + item_name + " # type: Incomplete\n") - - # del items - # del errors - # try: - # del item_name, item_repr, item_type_txt, item_instance # type: ignore - # except (OSError, KeyError, NameError): - # pass - - @property - def flat_fwid(self): - "Turn _fwid from 'v1.2.3' into '1_2_3' to be used in filename" - s = self._fwid - # path name restrictions - chars = " .()/\\:$" - for c in chars: - s = s.replace(c, "_") - return s - - def clean(self, path: str = None): # type: ignore - "Remove all files from the stub folder" - if path is None: - path = self.path - log.info("Clean/remove files in folder: {}".format(path)) - try: - os.stat(path) # TEMP workaround mpremote listdir bug - - items = os.listdir(path) - except (OSError, AttributeError): - # os.listdir fails on unix - return - for fn in items: - item = "{}/{}".format(path, fn) - try: - os.remove(item) - except OSError: - try: # folder - self.clean(item) - os.rmdir(item) - except OSError: - pass - - def report_start(self, filename: str = "modules.json"): - """Start a report of the modules that have been stubbed - "create json with list of exported modules""" - self._json_name = "{}/{}".format(self.path, filename) - self._json_first = True - ensure_folder(self._json_name) - log.info("Report file: {}".format(self._json_name)) - gc.collect() - try: - # write json by node to reduce memory requirements - with open(self._json_name, "w") as f: - f.write("{") - f.write(dumps({"firmware": self.info})[1:-1]) - f.write(",\n") - f.write(dumps({"stubber": {"version": __version__}, "stubtype": "firmware"})[1:-1]) - f.write(",\n") - f.write('"modules" :[\n') - - except OSError as e: - log.error("Failed to create the report.") - self._json_name = None - raise e - - def report_add(self, module_name: str, stub_file: str): - "Add a module to the report" - # write json by node to reduce memory requirements - if not self._json_name: - raise Exception("No report file") - try: - with open(self._json_name, "a") as f: - if not self._json_first: - f.write(",\n") - else: - self._json_first = False - line = '{{"module": "{}", "file": "{}"}}'.format(module_name, stub_file.replace("\\", "/")) - f.write(line) - - except OSError: - log.error("Failed to create the report.") - - def report_end(self): - if not self._json_name: - raise Exception("No report file") - with open(self._json_name, "a") as f: - f.write("\n]}") - # is used as sucess indicator - log.info("Path: {}".format(self.path)) - - -def ensure_folder(path: str): - "Create nested folders if needed" - i = start = 0 - while i != -1: - i = path.find("/", start) - if i != -1: - p = path[0] if i == 0 else path[:i] - # p = partial folder - try: - _ = os.stat(p) - except OSError as e: - # folder does not exist - if e.args[0] == ENOENT: - try: - os.mkdir(p) - except OSError as e2: - log.error("failed to create folder {}".format(p)) - raise e2 - # next level deep - start = i + 1 - - -def _build(s): - # extract build from sys.version or os.uname().version if available - # sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f' - # sys.implementation.version: 'v1.13-103-gb137d064e' - if not s: - return "" - s = s.split(" on ", 1)[0] if " on " in s else s - if s.startswith("v"): - if not "-" in s: - return "" - b = s.split("-")[1] - return b - if not "-preview" in s: - return "" - b = s.split("-preview")[1].split(".")[1] - return b - - -def _info(): # type:() -> dict[str, str] - try: - fam = sys.implementation[0] # type: ignore - except TypeError: - # testing on CPython 3.11 - fam = sys.implementation.name - - info = OrderedDict( - { - "family": fam, - "version": "", - "build": "", - "ver": "", - "port": sys.platform, # port: esp32 / win32 / linux / stm32 - "board": "UNKNOWN", - "cpu": "", - "mpy": "", - "arch": "", - } - ) - # change port names to be consistent with the repo - if info["port"].startswith("pyb"): - info["port"] = "stm32" - elif info["port"] == "win32": - info["port"] = "windows" - elif info["port"] == "linux": - info["port"] = "unix" - try: - info["version"] = version_str(sys.implementation.version) # type: ignore - except AttributeError: - pass - try: - _machine = ( - sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore - ) - # info["board"] = "with".join(_machine.split("with")[:-1]).strip() - info["board"] = _machine - info["cpu"] = _machine.split("with")[-1].strip() - info["mpy"] = ( - sys.implementation._mpy # type: ignore - if "_mpy" in dir(sys.implementation) - else sys.implementation.mpy if "mpy" in dir(sys.implementation) else "" # type: ignore - ) - except (AttributeError, IndexError): - pass - info["board"] = get_boardname() - - try: - if "uname" in dir(os): # old - # extract build from uname().version if available - info["build"] = _build(os.uname()[3]) # type: ignore - if not info["build"]: - # extract build from uname().release if available - info["build"] = _build(os.uname()[2]) # type: ignore - elif "version" in dir(sys): # new - # extract build from sys.version if available - info["build"] = _build(sys.version) - except (AttributeError, IndexError, TypeError): - pass - # avoid build hashes - # if info["build"] and len(info["build"]) > 5: - # info["build"] = "" - - if info["version"] == "" and sys.platform not in ("unix", "win32"): - try: - u = os.uname() # type: ignore - info["version"] = u.release - except (IndexError, AttributeError, TypeError): - pass - # detect families - for fam_name, mod_name, mod_thing in [ - ("pycopy", "pycopy", "const"), - ("pycom", "pycom", "FAT"), - ("ev3-pybricks", "pybricks.hubs", "EV3Brick"), - ]: - try: - _t = __import__(mod_name, None, None, (mod_thing)) - info["family"] = fam_name - del _t - break - except (ImportError, KeyError): - pass - - if info["family"] == "ev3-pybricks": - info["release"] = "2.0.0" - - if info["family"] == "micropython": - info["version"] - if ( - info["version"] - and info["version"].endswith(".0") - and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.23.0 do not have a micro .0 - and info["version"] <= "1.19.9" - ): - # versions from 1.10.0 to 1.23.0 do not have a micro .0 - info["version"] = info["version"][:-2] - - # spell-checker: disable - if "mpy" in info and info["mpy"]: # mpy on some v1.11+ builds - sys_mpy = int(info["mpy"]) - # .mpy architecture - arch = [ - None, - "x86", - "x64", - "armv6", - "armv6m", - "armv7m", - "armv7em", - "armv7emsp", - "armv7emdp", - "xtensa", - "xtensawin", - ][sys_mpy >> 10] - if arch: - info["arch"] = arch - # .mpy version.minor - info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3) - if info["build"] and not info["version"].endswith("-preview"): - info["version"] = info["version"] + "-preview" - # simple to use version[-build] string - info["ver"] = f"{info['version']}-{info['build']}" if info["build"] else f"{info['version']}" - - return info - - -def version_str(version: tuple): # -> str: - v_str = ".".join([str(n) for n in version[:3]]) - if len(version) > 3 and version[3]: - v_str += "-" + version[3] - return v_str - - -def get_boardname() -> str: - "Read the board name from the boardname.py file that may have been created upfront" - try: - from boardname import BOARDNAME # type: ignore - - log.info("Found BOARDNAME: {}".format(BOARDNAME)) - except ImportError: - log.warning("BOARDNAME not found") - BOARDNAME = "" - return BOARDNAME - - -def get_root() -> str: # sourcery skip: use-assigned-variable - "Determine the root folder of the device" - try: - c = os.getcwd() - except (OSError, AttributeError): - # unix port - c = "." - r = c - for r in [c, "/sd", "/flash", "/", "."]: - try: - _ = os.stat(r) - break - except OSError: - continue - return r - - -def file_exists(filename: str): - try: - if os.stat(filename)[0] >> 14: - return True - return False - except OSError: - return False - - -def show_help(): - print("-p, --path path to store the stubs in, defaults to '.'") - sys.exit(1) - - -def read_path() -> str: - "get --path from cmdline. [unix/win]" - path = "" - if len(sys.argv) == 3: - cmd = (sys.argv[1]).lower() - if cmd in ("--path", "-p"): - path = sys.argv[2] - else: - show_help() - elif len(sys.argv) == 2: - show_help() - return path - - -def is_micropython() -> bool: - "runtime test to determine full or micropython" - # pylint: disable=unused-variable,eval-used - try: - # either test should fail on micropython - - # b) https://docs.micropython.org/en/latest/genrst/builtin_types.html#bytes-with-keywords-not-implemented - # Micropython: NotImplementedError - b = bytes("abc", encoding="utf8") # type: ignore # lgtm [py/unused-local-variable] - - # c) https://docs.micropython.org/en/latest/genrst/core_language.html#function-objects-do-not-have-the-module-attribute - # Micropython: AttributeError - c = is_micropython.__module__ # type: ignore # lgtm [py/unused-local-variable] - return False - except (NotImplementedError, AttributeError): - return True - - -def main(): - stubber = Stubber(path=read_path()) - # stubber = Stubber(path="/sd") - # Option: Specify a firmware name & version - # stubber = Stubber(firmware_id='HoverBot v1.2.1') - stubber.clean() - # there is no option to discover modules from micropython, need to hardcode - # below contains combined modules from Micropython ESP8622, ESP32, Loboris, Pycom and ulab , lvgl - # spell-checker: disable - # modules to stub : 131 - stubber.modules = [ - "WM8960", - "_OTA", - "_asyncio", - "_boot_fat", - "_coap", - "_espnow", - "_flash_control_OTA", - "_main_pybytes", - "_mqtt", - "_mqtt_core", - "_msg_handl", - "_onewire", - "_periodical_pin", - "_pybytes", - "_pybytes_ca", - "_pybytes_config", - "_pybytes_config_reader", - "_pybytes_connection", - "_pybytes_constants", - "_pybytes_debug", - "_pybytes_library", - "_pybytes_machine_learning", - "_pybytes_main", - "_pybytes_protocol", - "_pybytes_pyconfig", - "_pybytes_pymesh_config", - "_rp2", - "_terminal", - "_thread", - "_uasyncio", - "_urequest", - "adcfft", - "aioble/__init__", - "aioble/central", - "aioble/client", - "aioble/core", - "aioble/device", - "aioble/l2cap", - "aioble/peripheral", - "aioble/security", - "aioble/server", - "aioespnow", - "ak8963", - "apa102", - "apa106", - "argparse", - "array", - "asyncio/__init__", - "asyncio/core", - "asyncio/event", - "asyncio/funcs", - "asyncio/lock", - "asyncio/stream", - "binascii", - "bluetooth", - "breakout_as7262", - "breakout_bh1745", - "breakout_bme280", - "breakout_bme68x", - "breakout_bmp280", - "breakout_dotmatrix", - "breakout_encoder", - "breakout_icp10125", - "breakout_ioexpander", - "breakout_ltr559", - "breakout_matrix11x7", - "breakout_mics6814", - "breakout_msa301", - "breakout_paa5100", - "breakout_pmw3901", - "breakout_potentiometer", - "breakout_rgbmatrix5x5", - "breakout_rtc", - "breakout_scd41", - "breakout_sgp30", - "breakout_trackball", - "breakout_vl53l5cx", - "btree", - "cmath", - "collections", - "crypto", - "cryptolib", - "curl", - "deflate", - "dht", - "display", - "display_driver_utils", - "ds18x20", - "encoder", - "errno", - "esp", - "esp32", - "espidf", - "espnow", - "ffi", - "flashbdev", - "framebuf", - "freesans20", - "fs_driver", - "functools", - "galactic", - "gc", - "gfx_pack", - "gsm", - "hashlib", - "heapq", - "hub75", - "ili9341", - "ili9XXX", - "imagetools", - "inisetup", - "interstate75", - "io", - "jpegdec", - "js", - "jsffi", - "json", - "lcd160cr", - "lodepng", - "logging", - "lsm6dsox", - "lv_colors", - "lv_utils", - "lvgl", - "lwip", - "machine", - "math", - "microWebSocket", - "microWebSrv", - "microWebTemplate", - "micropython", - "mip", - "mip/__init__", - "mip/__main__", - "motor", - "mpu6500", - "mpu9250", - "neopixel", - "network", - "ntptime", - "onewire", - "openamp", - "os", - "pcf85063a", - "picoexplorer", - "picographics", - "picokeypad", - "picoscroll", - "picounicorn", - "picowireless", - "pimoroni", - "pimoroni_bus", - "pimoroni_i2c", - "plasma", - "platform", - "pyb", - "pycom", - "pye", - "qrcode", - "queue", - "random", - "requests", - "requests/__init__", - "rp2", - "rtch", - "samd", - "select", - "servo", - "socket", - "ssd1306", - "ssh", - "ssl", - "stm", - "struct", - "sys", - "termios", - "time", - "tls", - "tpcalib", - "uarray", - "uasyncio/__init__", - "uasyncio/core", - "uasyncio/event", - "uasyncio/funcs", - "uasyncio/lock", - "uasyncio/stream", - "uasyncio/tasks", - "ubinascii", - "ubluetooth", - "ucollections", - "ucrypto", - "ucryptolib", - "uctypes", - "uerrno", - "uftpd", - "uhashlib", - "uheapq", - "uio", - "ujson", - "ulab", - "ulab/approx", - "ulab/compare", - "ulab/fft", - "ulab/filter", - "ulab/linalg", - "ulab/numerical", - "ulab/poly", - "ulab/user", - "ulab/vector", - "umachine", - "umqtt/__init__", - "umqtt/robust", - "umqtt/simple", - "uos", - "uplatform", - "uqueue", - "urandom", - "ure", - "urequests", - "urllib/urequest", - "usb/device", - "usb/device/cdc", - "usb/device/hid", - "usb/device/keyboard", - "usb/device/midi", - "usb/device/mouse", - "uselect", - "usocket", - "ussl", - "ustruct", - "usys", - "utelnetserver", - "utime", - "utimeq", - "uwebsocket", - "uzlib", - "version", - "vfs", - "websocket", - "websocket_helper", - "wipy", - "writer", - "xpt2046", - "ymodem", - "zephyr", - "zlib", - ] # spell-checker: enable - - gc.collect() - - stubber.create_all_stubs() - - -if __name__ == "__main__" or is_micropython(): - if not file_exists("no_auto_stubber.txt"): - try: - gc.threshold(4 * 1024) # type: ignore - gc.enable() - except BaseException: - pass - main() + def __init__(B,path=D,firmware_id=D): + C=firmware_id + try: + if os.uname().release=='1.13.0'and os.uname().version<'v1.13-103':raise k('MicroPython 1.13.0 cannot be stubbed') + except I:pass + B.info=_info();A.info('Port: {}'.format(B.info[K]));A.info('Board: {}'.format(B.info[S]));F.collect() + if C:B._fwid=C.lower() + elif B.info[O]==b:B._fwid='{family}-v{version}-{port}-{board}'.format(**B.info).rstrip(T) + else:B._fwid='{family}-v{version}-{port}'.format(**B.info) + B._start_free=F.mem_free() + if path: + if path.endswith(G):path=path[:-1] + else:path=get_root() + B.path='{}/stubs/{}'.format(path,B.flat_fwid).replace('//',G) + try:W(path+G) + except E:A.error('error creating stub folder {}'.format(path)) + B.problematic=['upip','upysh','webrepl_setup','http_client','http_client_ssl','http_server','http_server_ssl'];B.excluded=['webrepl','_webrepl','port_diag','example_sub_led.py','example_pub_button.py'];B.modules=[];B._json_name=D;B._json_first=H + def get_obj_attributes(L,item_instance): + H=item_instance;C=[];K=[] + for A in M(H): + if A.startswith('__')and not A in L.modules:continue + try: + D=getattr(H,A) + try:E=Z(type(D)).split("'")[1] + except P:E=B + if E in{p,q,r,s,c,d,e}:G=1 + elif E in{t,u}:G=2 + elif E in'class':G=3 + else:G=4 + C.append((A,Z(D),Z(type(D)),D,G)) + except I as J:K.append("Couldn't get attribute '{}' from object '{}', Err: {}".format(A,H,J)) + except MemoryError as J:Y('MemoryError: {}'.format(J));sleep(1);reset() + C=l([A for A in C if not A[0].startswith('__')],key=lambda x:x[4]);F.collect();return C,K + def add_modules(A,modules):A.modules=l(set(A.modules)|set(modules)) + def create_all_stubs(B): + A.info('Start micropython-stubber {} on {}'.format(__version__,B._fwid));B.report_start();F.collect() + for C in B.modules:B.create_one_stub(C) + B.report_end();A.info('Finally done') + def create_one_stub(C,module_name): + B=module_name + if B in C.problematic:A.warning('Skip module: {:<25} : Known problematic'.format(B));return H + if B in C.excluded:A.warning('Skip module: {:<25} : Excluded'.format(B));return H + I='{}/{}.pyi'.format(C.path,B.replace(J,G));F.collect();D=H + try:D=C.create_module_stub(B,I) + except E:return H + F.collect();return D + def create_module_stub(K,module_name,file_name=D): + I=file_name;C=module_name + if I is D:L=C.replace(J,'_')+'.pyi';I=K.path+G+L + else:L=I.split(G)[-1] + if G in C:C=C.replace(G,J) + M=D + try:M=__import__(C,D,D,'*');P=F.mem_free();A.info('Stub module: {:<25} to file: {:<70} mem:{:>5}'.format(C,L,P)) + except N:return H + W(I) + with Q(I,'w')as O:R=str(K.info).replace('OrderedDict(',B).replace('})','}');S='"""\nModule: \'{0}\' on {1}\n"""\n# MCU: {2}\n# Stubber: {3}\n'.format(C,K._fwid,R,__version__);O.write(S);O.write('from __future__ import annotations\nfrom typing import Any, Generator\nfrom _typeshed import Incomplete\n\n');K.write_object_stub(O,M,C,B) + K.report_add(C,I) + if C not in{'os',v,w,'gc'}: + try:del M + except(E,m):A.warning('could not del new_module') + F.collect();return U + def write_object_stub(L,fp,object_expr,obj_name,indent,in_class=0): + Z=' at ...>';Y='generator';X='{0}{1}: {3} = {2}\n';W='bound_method';V='Incomplete';O=in_class;N='Exception';M=object_expr;K=' at ';J=fp;D=indent;F.collect() + if M in L.problematic:A.warning('SKIPPING problematic module:{}'.format(M));return + a,P=L.get_obj_attributes(M) + if P:A.error(P) + for(E,H,I,b,g)in a: + if E in['classmethod','staticmethod','BaseException',N]:continue + if E[0].isdigit():A.warning('NameError: invalid name {}'.format(E));continue + if I==""and R(D)<=A1*4: + Q=B;S=E.endswith(N)or E.endswith('Error')or E in['KeyboardInterrupt','StopIteration','SystemExit'] + if S:Q=N + C='\n{}class {}({}):\n'.format(D,E,Q) + if S:C+=D+' ...\n';J.write(C);continue + J.write(C);L.write_object_stub(J,b,'{0}.{1}'.format(obj_name,E),D+' ',O+1);C=D+' def __init__(self, *argv, **kwargs) -> None:\n';C+=D+' ...\n\n';J.write(C) + elif any(A in I for A in[u,t,'closure']): + T=V;U=B + if O>0:U='self, ' + if W in I or W in H:C='{}@classmethod\n'.format(D)+'{}def {}(cls, *args, **kwargs) -> {}:\n'.format(D,E,T) + else:C='{}def {}({}*args, **kwargs) -> {}:\n'.format(D,E,U,T) + C+=D+' ...\n\n';J.write(C) + elif I=="":0 + elif I.startswith("='1.10.0'and A[C]<='1.19.9':A[C]=A[C][:-2] + if F in A and A[F]: + G=int(A[F]);L=[D,'x86','x64','armv6','armv6m','armv7m','armv7em','armv7emsp','armv7emdp','xtensa','xtensawin'][G>>10] + if L:A[T]=L + A[F]='v{}.{}'.format(G&255,G>>8&3) + if A[E]and not A[C].endswith(V):A[C]=A[C]+V + A[Q]=f"{A[C]}-{A[E]}"if A[E]else f"{A[C]}";return A +def A2(version): + A=version;B=J.join([str(A)for A in A[:3]]) + if R(A)>3 and A[3]:B+=T+A[3] + return B +def A3(): + try:from boardname import BOARDNAME as C;A.info('Found BOARDNAME: {}'.format(C)) + except N:A.warning('BOARDNAME not found');C=B + return C +def get_root(): + try:A=os.getcwd() + except(E,I):A=J + B=A + for B in[A,'/sd','/flash',G,J]: + try:C=os.stat(B);break + except E:continue + return B +def A4(filename): + try: + if os.stat(filename)[0]>>14:return U + return H + except E:return H +def i():Y("-p, --path path to store the stubs in, defaults to '.'");sys.exit(1) +def read_path(): + path=B + if R(sys.argv)==3: + A=sys.argv[1].lower() + if A in('--path','-p'):path=sys.argv[2] + else:i() + elif R(sys.argv)==2:i() + return path +def j(): + try:A=bytes('abc',encoding='utf8');B=j.__module__;return H + except(k,I):return U +def main():stubber=Stubber(path=read_path());stubber.clean();stubber.modules=['WM8960','_OTA','_asyncio','_boot_fat','_coap','_espnow','_flash_control_OTA','_main_pybytes','_mqtt','_mqtt_core','_msg_handl','_onewire','_periodical_pin','_pybytes','_pybytes_ca','_pybytes_config','_pybytes_config_reader','_pybytes_connection','_pybytes_constants','_pybytes_debug','_pybytes_library','_pybytes_machine_learning','_pybytes_main','_pybytes_protocol','_pybytes_pyconfig','_pybytes_pymesh_config','_rp2','_terminal','_thread','_uasyncio','_urequest','adcfft','aioble/__init__','aioble/central','aioble/client','aioble/core','aioble/device','aioble/l2cap','aioble/peripheral','aioble/security','aioble/server','aioespnow','ak8963','apa102','apa106','argparse','array','asyncio/__init__','asyncio/core','asyncio/event','asyncio/funcs','asyncio/lock','asyncio/stream','binascii','bluetooth','breakout_as7262','breakout_bh1745','breakout_bme280','breakout_bme68x','breakout_bmp280','breakout_dotmatrix','breakout_encoder','breakout_icp10125','breakout_ioexpander','breakout_ltr559','breakout_matrix11x7','breakout_mics6814','breakout_msa301','breakout_paa5100','breakout_pmw3901','breakout_potentiometer','breakout_rgbmatrix5x5','breakout_rtc','breakout_scd41','breakout_sgp30','breakout_trackball','breakout_vl53l5cx','btree','cmath','collections','crypto','cryptolib','curl','deflate','dht','display','display_driver_utils','ds18x20','encoder','errno','esp','esp32','espidf','espnow','ffi','flashbdev','framebuf','freesans20','fs_driver','functools','galactic','gc','gfx_pack','gsm','hashlib','heapq','hub75','ili9341','ili9XXX','imagetools','inisetup','interstate75','io','jpegdec','js','jsffi','json','lcd160cr','lodepng',w,'lsm6dsox','lv_colors','lv_utils','lvgl','lwip','machine','math','microWebSocket','microWebSrv','microWebTemplate',b,'mip','mip/__init__','mip/__main__','motor','mpu6500','mpu9250','neopixel','network','ntptime','onewire','openamp','os','pcf85063a','picoexplorer','picographics','picokeypad','picoscroll','picounicorn','picowireless','pimoroni','pimoroni_bus','pimoroni_i2c','plasma','platform','pyb',g,'pye','qrcode','queue','random','requests','requests/__init__','rp2','rtch','samd','select','servo','socket','ssd1306','ssh','ssl','stm','struct',v,'termios','time','tls','tpcalib','uarray','uasyncio/__init__','uasyncio/core','uasyncio/event','uasyncio/funcs','uasyncio/lock','uasyncio/stream','uasyncio/tasks','ubinascii','ubluetooth','ucollections','ucrypto','ucryptolib','uctypes','uerrno','uftpd','uhashlib','uheapq','uio','ujson','ulab','ulab/approx','ulab/compare','ulab/fft','ulab/filter','ulab/linalg','ulab/numerical','ulab/poly','ulab/user','ulab/vector','umachine','umqtt/__init__','umqtt/robust','umqtt/simple','uos','uplatform','uqueue','urandom','ure','urequests','urllib/urequest','usb/device','usb/device/cdc','usb/device/hid','usb/device/keyboard','usb/device/midi','usb/device/mouse','uselect','usocket','ussl','ustruct','usys','utelnetserver','utime','utimeq','uwebsocket','uzlib',C,'vfs','websocket','websocket_helper','wipy','writer','xpt2046','ymodem','zephyr','zlib'];F.collect();stubber.create_all_stubs() +if __name__=='__main__'or j(): + if not A4('no_auto_stubber.txt'): + try:F.threshold(4*1024);F.enable() + except BaseException:pass + main() \ No newline at end of file diff --git a/src/stubber/board/createstubs_mpy.mpy b/src/stubber/board/createstubs_mpy.mpy index 9a6018bcfbaf314abfb7d99dfea52a5a77fc464b..b06bf5d6308fab9eaa815d3e32f59ac93091036d 100644 GIT binary patch delta 7071 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