mpflash 1.24.6__py3-none-any.whl → 1.24.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. mpflash/ask_input.py +7 -7
  2. mpflash/basicgit.py +23 -57
  3. mpflash/bootloader/__init__.py +0 -2
  4. mpflash/bootloader/activate.py +1 -1
  5. mpflash/bootloader/detect.py +1 -2
  6. mpflash/bootloader/manual.py +0 -1
  7. mpflash/bootloader/touch1200.py +2 -2
  8. mpflash/cli_flash.py +28 -5
  9. mpflash/cli_group.py +1 -0
  10. mpflash/cli_list.py +2 -2
  11. mpflash/cli_main.py +1 -1
  12. mpflash/common.py +6 -14
  13. mpflash/config.py +26 -7
  14. mpflash/connected.py +6 -14
  15. mpflash/download.py +56 -23
  16. mpflash/downloaded.py +1 -5
  17. mpflash/flash/__init__.py +33 -18
  18. mpflash/flash/esp.py +40 -9
  19. mpflash/flash/uf2/__init__.py +18 -2
  20. mpflash/flash/uf2/linux.py +4 -9
  21. mpflash/flash/uf2/macos.py +1 -1
  22. mpflash/flash/uf2/windows.py +1 -1
  23. mpflash/flash/worklist.py +7 -2
  24. mpflash/list.py +17 -6
  25. mpflash/logger.py +1 -3
  26. mpflash/mpboard_id/__init__.py +6 -87
  27. mpflash/mpboard_id/add_boards.py +6 -8
  28. mpflash/mpboard_id/board_id.py +7 -6
  29. mpflash/mpboard_id/board_info.json +30974 -0
  30. mpflash/mpboard_id/board_info.zip +0 -0
  31. mpflash/mpboard_id/known.py +94 -0
  32. mpflash/mpboard_id/store.py +0 -2
  33. mpflash/mpremoteboard/__init__.py +13 -9
  34. mpflash/mpremoteboard/mpy_fw_info.py +14 -17
  35. mpflash/py.typed +0 -0
  36. mpflash/vendor/click_aliases.py +64 -0
  37. mpflash/vendor/dfu.py +2 -8
  38. mpflash/vendor/pico-universal-flash-nuke/LICENSE.txt +21 -0
  39. mpflash/vendor/pico-universal-flash-nuke/universal_flash_nuke.uf2 +0 -0
  40. mpflash/vendor/pydfu.py +3 -14
  41. mpflash/vendor/readme.md +2 -0
  42. mpflash/versions.py +9 -6
  43. {mpflash-1.24.6.dist-info → mpflash-1.24.8.dist-info}/METADATA +71 -13
  44. mpflash-1.24.8.dist-info/RECORD +59 -0
  45. {mpflash-1.24.6.dist-info → mpflash-1.24.8.dist-info}/WHEEL +1 -1
  46. mpflash-1.24.6.dist-info/RECORD +0 -54
  47. {mpflash-1.24.6.dist-info → mpflash-1.24.8.dist-info}/LICENSE +0 -0
  48. {mpflash-1.24.6.dist-info → mpflash-1.24.8.dist-info}/entry_points.txt +0 -0
Binary file
@@ -0,0 +1,94 @@
1
+ """
2
+ KNOWN ports and boards are sourced from the micropython repo,
3
+ this info is stored in the board_info.json file
4
+ and is used to identify the board and port for flashing.
5
+ This module provides access to the board info and the known ports and boards."""
6
+
7
+ from functools import lru_cache
8
+ from typing import List, Optional, Tuple
9
+
10
+ from mpflash.errors import MPFlashError
11
+ from mpflash.versions import clean_version
12
+
13
+ from .board import Board
14
+ from .store import read_known_boardinfo
15
+
16
+
17
+ def get_known_ports() -> List[str]:
18
+ # TODO: Filter for Version
19
+ mp_boards = read_known_boardinfo()
20
+ # select the unique ports from info
21
+ ports = set({board.port for board in mp_boards if board.port})
22
+ return sorted(list(ports))
23
+
24
+
25
+ def get_known_boards_for_port(port: Optional[str] = "", versions: Optional[List[str]] = None) -> List[Board]:
26
+ """
27
+ Returns a list of boards for the given port and version(s)
28
+
29
+ port: The Micropython port to filter for
30
+ versions: Optional, The Micropython versions to filter for (actual versions required)
31
+ """
32
+ mp_boards = read_known_boardinfo()
33
+ if versions:
34
+ preview_or_stable = "preview" in versions or "stable" in versions
35
+ else:
36
+ preview_or_stable = False
37
+
38
+ # filter for 'preview' as they are not in the board_info.json
39
+ # instead use stable version
40
+ versions = versions or []
41
+ if "preview" in versions:
42
+ versions.remove("preview")
43
+ versions.append("stable")
44
+ if versions:
45
+ # make sure of the v prefix
46
+ versions = [clean_version(v) for v in versions]
47
+ # filter for the version(s)
48
+ mp_boards = [board for board in mp_boards if board.version in versions]
49
+ if not mp_boards and preview_or_stable:
50
+ # nothing found - perhaps there is a newer version for which we do not have the board info yet
51
+ # use the latest known version from the board info
52
+ mp_boards = read_known_boardinfo()
53
+ last_known_version = sorted({b.version for b in mp_boards})[-1]
54
+ mp_boards = [board for board in mp_boards if board.version == last_known_version]
55
+
56
+ # filter for the port
57
+ if port:
58
+ mp_boards = [board for board in mp_boards if board.port == port]
59
+ return mp_boards
60
+
61
+
62
+ def known_stored_boards(port: str, versions: Optional[List[str]] = None) -> List[Tuple[str, str]]:
63
+ """
64
+ Returns a list of tuples with the description and board name for the given port and version
65
+
66
+ port : str : The Micropython port to filter for
67
+ versions : List[str] : The Micropython versions to filter for (actual versions required)
68
+ """
69
+ mp_boards = get_known_boards_for_port(port, versions)
70
+
71
+ boards = set({(f"{board.version} {board.description}", board.board_id) for board in mp_boards})
72
+ return sorted(list(boards))
73
+
74
+
75
+ @lru_cache(maxsize=20)
76
+ def find_known_board(board_id: str) -> Board:
77
+ """Find the board for the given BOARD_ID or 'board description' and return the board info as a Board object"""
78
+ # Some functional overlap with:
79
+ # mpboard_id\board_id.py _find_board_id_by_description
80
+ info = read_known_boardinfo()
81
+ for board_info in info:
82
+ if board_id in (
83
+ board_info.board_id,
84
+ board_info.description,
85
+ ) or board_info.description.startswith(board_id):
86
+ if not board_info.cpu:
87
+ # failsafe for older board_info.json files
88
+ print(f"Board {board_id} has no CPU info, using port as CPU")
89
+ if " with " in board_info.description:
90
+ board_info.cpu = board_info.description.split(" with ")[-1]
91
+ else:
92
+ board_info.cpu = board_info.port
93
+ return board_info
94
+ raise MPFlashError(f"Board {board_id} not found")
@@ -19,7 +19,6 @@ def write_boardinfo_json(board_list: List[Board], *, folder: Optional[Path] = No
19
19
  board_list (List[Board]): The list of Board objects.
20
20
  folder (Path): The folder where the compressed JSON file will be saved.
21
21
  """
22
- import zipfile
23
22
 
24
23
  if not folder:
25
24
  folder = HERE
@@ -34,7 +33,6 @@ def write_boardinfo_json(board_list: List[Board], *, folder: Optional[Path] = No
34
33
  def read_known_boardinfo(board_info: Optional[Path] = None) -> List[Board]:
35
34
  """Reads the board information from a JSON file in a zip file."""
36
35
 
37
- import zipfile
38
36
 
39
37
  if not board_info:
40
38
  board_info = HERE / "board_info.zip"
@@ -2,6 +2,8 @@
2
2
  Module to run mpremote commands, and retry on failure or timeout
3
3
  """
4
4
 
5
+
6
+ import contextlib
5
7
  import sys
6
8
  import time
7
9
  from pathlib import Path
@@ -51,6 +53,7 @@ class MPRemoteBoard:
51
53
  self.version = ""
52
54
  self.port = ""
53
55
  self.board = ""
56
+ self.variant= ""
54
57
  self.cpu = ""
55
58
  self.arch = ""
56
59
  self.mpy = ""
@@ -65,9 +68,9 @@ class MPRemoteBoard:
65
68
  Return a string representation of the MPRemoteBoard object.
66
69
 
67
70
  Returns:
68
- - str: The string representation of the object.
71
+ - str: A human readable representation of the MCU.
69
72
  """
70
- return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}, {self.version})"
73
+ return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}{f'-{self.variant}' if self.variant else ''}, {self.version})"
71
74
 
72
75
  @staticmethod
73
76
  def connected_boards(bluetooth: bool = False, description: bool = False) -> List[str]:
@@ -137,9 +140,12 @@ class MPRemoteBoard:
137
140
  self.description = descr = info["board"]
138
141
  pos = descr.rfind(" with")
139
142
  short_descr = descr[:pos].strip() if pos != -1 else ""
140
- if board_name := find_board_id_by_description(
141
- descr, short_descr, version=self.version
142
- ):
143
+ if info["_build"]:
144
+ self.board = info["_build"].split('-')[0]
145
+ self.variant = info["_build"].split('-')[1] if '-' in info["_build"] else ""
146
+ elif board_name := find_board_id_by_description(
147
+ descr, short_descr, version=self.version
148
+ ):
143
149
  self.board = board_name
144
150
  else:
145
151
  self.board = "UNKNOWN_BOARD"
@@ -168,7 +174,7 @@ class MPRemoteBoard:
168
174
  log_errors=False,
169
175
  )
170
176
  except Exception as e:
171
- raise ConnectionError(f"Failed to get board_info.toml for {self.serialport}: {e}")
177
+ raise ConnectionError(f"Failed to get board_info.toml for {self.serialport}:") from e
172
178
  # this is optional - so only parse if we got the file
173
179
  self.toml = {}
174
180
  if rc in [OK]: # sometimes we get an -9 ???
@@ -264,8 +270,6 @@ class MPRemoteBoard:
264
270
  total=timeout,
265
271
  ):
266
272
  time.sleep(1)
267
- try:
273
+ with contextlib.suppress(ConnectionError, MPFlashError):
268
274
  self.get_mcu_info()
269
275
  break
270
- except (ConnectionError, MPFlashError):
271
- pass
@@ -1,4 +1,4 @@
1
- # %%micropython
1
+ # pragma: no cover
2
2
  import os
3
3
  import sys
4
4
 
@@ -11,11 +11,11 @@ def _build(s):
11
11
  return ""
12
12
  s = s.split(" on ", 1)[0] if " on " in s else s
13
13
  if s.startswith("v"):
14
- if not "-" in s:
14
+ if "-" not in s:
15
15
  return ""
16
16
  b = s.split("-")[1]
17
17
  return b
18
- if not "-preview" in s:
18
+ if "-preview" not in s:
19
19
  return ""
20
20
  b = s.split("-preview")[1].split(".")[1]
21
21
  return b
@@ -36,10 +36,9 @@ def _info(): # type:() -> dict[str, str]
36
36
  "version": "",
37
37
  "build": "",
38
38
  "ver": "",
39
- "port": (
40
- "stm32" if sys.platform.startswith("pyb") else sys.platform
41
- ), # port: esp32 / win32 / linux / stm32
39
+ "port": ("stm32" if sys.platform.startswith("pyb") else sys.platform), # port: esp32 / win32 / linux / stm32
42
40
  "board": "GENERIC",
41
+ "_build": "",
43
42
  "cpu": "",
44
43
  "mpy": "",
45
44
  "arch": "",
@@ -50,17 +49,16 @@ def _info(): # type:() -> dict[str, str]
50
49
  except AttributeError:
51
50
  pass
52
51
  try:
53
- machine = (
54
- sys.implementation._machine
55
- if "_machine" in dir(sys.implementation)
56
- else os.uname().machine
57
- )
52
+ machine = sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine # type: ignore
58
53
  info["board"] = machine.strip()
54
+ info["_build"] = sys.implementation._build if "_build" in dir(sys.implementation) else ""
59
55
  info["cpu"] = machine.split("with")[-1].strip() if "with" in machine else ""
60
56
  info["mpy"] = (
61
57
  sys.implementation._mpy
62
58
  if "_mpy" in dir(sys.implementation)
63
- else sys.implementation.mpy if "mpy" in dir(sys.implementation) else ""
59
+ else sys.implementation.mpy
60
+ if "mpy" in dir(sys.implementation)
61
+ else ""
64
62
  )
65
63
  except (AttributeError, IndexError):
66
64
  pass
@@ -69,10 +67,10 @@ def _info(): # type:() -> dict[str, str]
69
67
  if hasattr(sys, "version"):
70
68
  info["build"] = _build(sys.version)
71
69
  elif hasattr(os, "uname"):
72
- info["build"] = _build(os.uname()[3])
70
+ info["build"] = _build(os.uname()[3]) # type: ignore
73
71
  if not info["build"]:
74
72
  # extract build from uname().release if available
75
- info["build"] = _build(os.uname()[2])
73
+ info["build"] = _build(os.uname()[2]) # type: ignore
76
74
  except (AttributeError, IndexError):
77
75
  pass
78
76
  # avoid build hashes
@@ -81,7 +79,7 @@ def _info(): # type:() -> dict[str, str]
81
79
 
82
80
  if info["version"] == "" and sys.platform not in ("unix", "win32"):
83
81
  try:
84
- u = os.uname()
82
+ u = os.uname() # type: ignore
85
83
  info["version"] = u.release
86
84
  except (IndexError, AttributeError, TypeError):
87
85
  pass
@@ -106,8 +104,7 @@ def _info(): # type:() -> dict[str, str]
106
104
  if (
107
105
  info["version"]
108
106
  and info["version"].endswith(".0")
109
- and info["version"]
110
- >= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
107
+ and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
111
108
  and info["version"] <= "1.19.9"
112
109
  ):
113
110
  # drop the .0 for newer releases
mpflash/py.typed ADDED
File without changes
@@ -10,6 +10,7 @@
10
10
  # The above copyright notice and this permission notice shall be included in all
11
11
  # copies or substantial portions of the Software.
12
12
  # ------------------------------------------------------------------------------------
13
+ # Jos Verlinde - 2024
13
14
  # modified to avoid conflcts with rich_click
14
15
 
15
16
  # sourcery skip: assign-if-exp, use-named-expression
@@ -20,12 +21,36 @@ _click7 = click.__version__[0] >= "7"
20
21
 
21
22
 
22
23
  class ClickAliasedGroup(click.RichGroup):
24
+ """
25
+ A subclass of click.RichGroup that adds support for command aliases.
26
+
27
+ This class allows defining aliases for commands and groups, enabling users
28
+ to invoke commands using alternative names.
29
+ """
30
+
23
31
  def __init__(self, *args, **kwargs):
32
+ """
33
+ Initialize the ClickAliasedGroup instance.
34
+
35
+ Args:
36
+ *args: Positional arguments passed to the superclass.
37
+ **kwargs: Keyword arguments passed to the superclass.
38
+ """
24
39
  super().__init__(*args, **kwargs)
25
40
  self._commands = {}
26
41
  self._aliases = {}
27
42
 
28
43
  def add_command(self, *args, **kwargs):
44
+ """
45
+ Add a command to the group, optionally with aliases.
46
+
47
+ Args:
48
+ *args: Positional arguments, typically the command instance and optionally its name.
49
+ **kwargs: Keyword arguments, may include 'aliases' as a list of alternative names.
50
+
51
+ Raises:
52
+ TypeError: If the command has no name.
53
+ """
29
54
  aliases = kwargs.pop("aliases", [])
30
55
  super().add_command(*args, **kwargs)
31
56
  if aliases:
@@ -40,6 +65,16 @@ class ClickAliasedGroup(click.RichGroup):
40
65
  self._aliases[alias] = cmd.name
41
66
 
42
67
  def command(self, *args, **kwargs):
68
+ """
69
+ Decorator to define a new command with optional aliases.
70
+
71
+ Args:
72
+ *args: Positional arguments passed to the superclass decorator.
73
+ **kwargs: Keyword arguments, may include 'aliases' as a list of alternative names.
74
+
75
+ Returns:
76
+ Callable: A decorator function that registers the command and its aliases.
77
+ """
43
78
  aliases = kwargs.pop("aliases", [])
44
79
  decorator = super().command(*args, **kwargs)
45
80
  if not aliases:
@@ -56,6 +91,16 @@ class ClickAliasedGroup(click.RichGroup):
56
91
  return _decorator
57
92
 
58
93
  def group(self, *args, **kwargs):
94
+ """
95
+ Decorator to define a new command group with optional aliases.
96
+
97
+ Args:
98
+ *args: Positional arguments passed to the superclass decorator.
99
+ **kwargs: Keyword arguments, may include 'aliases' as a list of alternative names.
100
+
101
+ Returns:
102
+ Callable: A decorator function that registers the group and its aliases.
103
+ """
59
104
  aliases = kwargs.pop("aliases", [])
60
105
  decorator = super().group(*args, **kwargs)
61
106
  if not aliases:
@@ -72,11 +117,30 @@ class ClickAliasedGroup(click.RichGroup):
72
117
  return _decorator
73
118
 
74
119
  def resolve_alias(self, cmd_name):
120
+ """
121
+ Resolve a command alias to its original command name.
122
+
123
+ Args:
124
+ cmd_name (str): The command name or alias to resolve.
125
+
126
+ Returns:
127
+ str: The original command name if an alias is provided; otherwise, the input name.
128
+ """
75
129
  if cmd_name in self._aliases:
76
130
  return self._aliases[cmd_name]
77
131
  return cmd_name
78
132
 
79
133
  def get_command(self, ctx, cmd_name):
134
+ """
135
+ Retrieve a command by name or alias.
136
+
137
+ Args:
138
+ ctx (click.Context): The Click context object.
139
+ cmd_name (str): The command name or alias to retrieve.
140
+
141
+ Returns:
142
+ click.Command or None: The command object if found; otherwise, None.
143
+ """
80
144
  cmd_name = self.resolve_alias(cmd_name)
81
145
  command = super().get_command(ctx, cmd_name)
82
146
  if command:
mpflash/vendor/dfu.py CHANGED
@@ -46,10 +46,7 @@ def parse(file, dump_images=False):
46
46
  tprefix["name"] = cstring(tprefix["name"])
47
47
  else:
48
48
  tprefix["name"] = ""
49
- print(
50
- '%(signature)s %(num)d, alt setting: %(altsetting)s, name: "%(name)s", size: %(size)d, elements: %(elements)d'
51
- % tprefix
52
- )
49
+ print('%(signature)s %(num)d, alt setting: %(altsetting)s, name: "%(name)s", size: %(size)d, elements: %(elements)d' % tprefix)
53
50
  tsize = tprefix["size"]
54
51
  target, data = data[:tsize], data[tsize:]
55
52
  for e in range(tprefix["elements"]):
@@ -65,10 +62,7 @@ def parse(file, dump_images=False):
65
62
  if len(target):
66
63
  print("target %d: PARSE ERROR" % t)
67
64
  suffix = named(struct.unpack("<4H3sBI", data[:16]), "device product vendor dfu ufd len crc")
68
- print(
69
- "usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x"
70
- % suffix
71
- )
65
+ print("usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x" % suffix)
72
66
  if crc != suffix["crc"]:
73
67
  print("CRC ERROR: computed crc32 is 0x%08x" % crc)
74
68
  data = data[16:]
@@ -0,0 +1,21 @@
1
+ Copyright 2024 Phil Howard
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4
+ following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7
+ disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
10
+ disclaimer in the documentation and/or other materials provided with the distribution.
11
+
12
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
21
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
mpflash/vendor/pydfu.py CHANGED
@@ -395,10 +395,7 @@ def read_dfu_file(filename):
395
395
  # B uint8_t len 16
396
396
  # I uint32_t crc32 Checksum
397
397
  dfu_suffix = named(struct.unpack("<4H3sBI", data[:16]), "device product vendor dfu ufd len crc")
398
- print(
399
- " usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, "
400
- "dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x" % dfu_suffix
401
- )
398
+ print(" usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, " "dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x" % dfu_suffix)
402
399
  if crc != dfu_suffix["crc"]:
403
400
  print("CRC ERROR: computed crc32 is 0x%08x" % crc)
404
401
  return
@@ -477,19 +474,11 @@ def list_dfu_devices(*args, **kwargs):
477
474
  if not devices:
478
475
  raise SystemExit("No DFU capable devices found")
479
476
  for device in devices:
480
- print(
481
- "Bus {} Device {:03d}: ID {:04x}:{:04x}".format(
482
- device.bus, device.address, device.idVendor, device.idProduct
483
- )
484
- )
477
+ print("Bus {} Device {:03d}: ID {:04x}:{:04x}".format(device.bus, device.address, device.idVendor, device.idProduct))
485
478
  layout = get_memory_layout(device)
486
479
  print("Memory Layout")
487
480
  for entry in layout:
488
- print(
489
- " 0x{:x} {:2d} pages of {:3d}K bytes".format(
490
- entry["addr"], entry["num_pages"], entry["page_size"] // 1024
491
- )
492
- )
481
+ print(" 0x{:x} {:2d} pages of {:3d}K bytes".format(entry["addr"], entry["num_pages"], entry["page_size"] // 1024))
493
482
 
494
483
 
495
484
  def write_elements(elements, mass_erase_used, progress=None):
mpflash/vendor/readme.md CHANGED
@@ -10,3 +10,5 @@ These modules are vendored from the following repositories:
10
10
  - https://github.com/click-contrib/click-aliases (Public Domain)
11
11
  - click_aliases.py (Robbin Bonthond)
12
12
 
13
+ - https://github.com/Gadgetoid/pico-universal-flash-nuke
14
+ - universal_flash_nuke.uf2
mpflash/versions.py CHANGED
@@ -6,10 +6,8 @@ from pathlib import Path
6
6
 
7
7
  from cache_to_disk import NoCacheCondition, cache_to_disk
8
8
  from loguru import logger as log
9
- from packaging.version import Version, parse
10
9
 
11
- import mpflash.basicgit as git
12
- from mpflash.common import GH_CLIENT
10
+ from mpflash.config import config
13
11
 
14
12
  OLDEST_VERSION = "1.16"
15
13
  "This is the oldest MicroPython version to build the stubs on"
@@ -71,15 +69,18 @@ def clean_version(
71
69
 
72
70
  def is_version(version: str):
73
71
  """Check if the version is a valid version string"""
72
+ # Just in time import
73
+ from packaging.version import Version
74
74
  return Version._regex.search(version) is not None
75
75
 
76
76
 
77
77
  @cache_to_disk(n_days_to_cache=1)
78
78
  def micropython_versions(minver: str = "v1.20", reverse: bool = False, cache_it=True):
79
79
  """Get the list of micropython versions from github tags"""
80
-
80
+ # Just in time import
81
+ from packaging.version import parse
81
82
  try:
82
- gh_client = GH_CLIENT
83
+ gh_client = config.gh_client
83
84
  repo = gh_client.get_repo("micropython/micropython")
84
85
  tags = [tag.name for tag in repo.get_tags() if parse(tag.name) >= parse(minver)]
85
86
  versions = [v for v in tags if not v.endswith(V_PREVIEW)]
@@ -90,7 +91,7 @@ def micropython_versions(minver: str = "v1.20", reverse: bool = False, cache_it=
90
91
  log.error(e)
91
92
  versions = []
92
93
  # returns - but does not cache
93
- raise NoCacheCondition(function_value=versions)
94
+ raise NoCacheCondition(function_value=versions) from e
94
95
  # remove any duplicates and sort
95
96
  versions = sorted(list(set(versions)), reverse=reverse, key=lambda s: (not is_version(s), s))
96
97
  if cache_it:
@@ -116,6 +117,8 @@ def get_preview_mp_version(cache_it=True) -> str:
116
117
  # Do not cache , same path will have different versions checked out
117
118
  def checkedout_version(path: Path, flat: bool = False) -> str:
118
119
  """Get the checked-out version of the repo"""
120
+ # Just in time import
121
+ import mpflash.basicgit as git
119
122
  version = git.get_local_tag(path.as_posix())
120
123
  if not version:
121
124
  raise ValueError("No valid Tag found")