circfirm 3.1.0__tar.gz → 4.0.1__tar.gz

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 (122) hide show
  1. {circfirm-3.1.0 → circfirm-4.0.1}/.github/workflows/push.yml +5 -5
  2. {circfirm-3.1.0 → circfirm-4.0.1}/.pre-commit-config.yaml +1 -1
  3. {circfirm-3.1.0 → circfirm-4.0.1}/Makefile +2 -2
  4. {circfirm-3.1.0 → circfirm-4.0.1}/PKG-INFO +17 -16
  5. circfirm-4.0.1/VERSION +1 -0
  6. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/backend/__init__.py +1 -2
  7. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/backend/cache.py +8 -8
  8. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/backend/device.py +2 -2
  9. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/backend/github.py +4 -4
  10. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/backend/s3.py +2 -3
  11. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/cli/__init__.py +21 -7
  12. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/cli/cache.py +5 -0
  13. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/cli/current.py +1 -3
  14. circfirm-4.0.1/circfirm/cli/detect.py +38 -0
  15. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/cli/install.py +13 -2
  16. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/cli/query.py +24 -5
  17. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/cli/update.py +19 -4
  18. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/startup.py +3 -4
  19. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm.egg-info/PKG-INFO +17 -16
  20. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm.egg-info/SOURCES.txt +3 -0
  21. circfirm-4.0.1/circfirm.egg-info/requires.txt +17 -0
  22. circfirm-4.0.1/docs/commands/detect.rst +30 -0
  23. {circfirm-3.1.0 → circfirm-4.0.1}/docs/commands/install.rst +8 -0
  24. {circfirm-3.1.0 → circfirm-4.0.1}/docs/commands/update.rst +8 -0
  25. {circfirm-3.1.0 → circfirm-4.0.1}/docs/conf.py +0 -1
  26. {circfirm-3.1.0 → circfirm-4.0.1}/docs/index.rst +1 -0
  27. {circfirm-3.1.0 → circfirm-4.0.1}/pyproject.toml +4 -3
  28. {circfirm-3.1.0 → circfirm-4.0.1}/requirements-dev.txt +6 -6
  29. {circfirm-3.1.0 → circfirm-4.0.1}/requirements.txt +6 -6
  30. {circfirm-3.1.0 → circfirm-4.0.1}/scripts/rmdir.py +8 -0
  31. {circfirm-3.1.0 → circfirm-4.0.1}/tests/backend/test_backend.py +1 -0
  32. {circfirm-3.1.0 → circfirm-4.0.1}/tests/backend/test_backend_device.py +5 -10
  33. {circfirm-3.1.0 → circfirm-4.0.1}/tests/backend/test_backend_s3.py +2 -3
  34. {circfirm-3.1.0 → circfirm-4.0.1}/tests/cli/test_cli_cache.py +34 -19
  35. {circfirm-3.1.0 → circfirm-4.0.1}/tests/cli/test_cli_current.py +2 -5
  36. circfirm-4.0.1/tests/cli/test_cli_detect.py +52 -0
  37. {circfirm-3.1.0 → circfirm-4.0.1}/tests/cli/test_cli_install.py +44 -10
  38. {circfirm-3.1.0 → circfirm-4.0.1}/tests/cli/test_cli_query.py +77 -20
  39. {circfirm-3.1.0 → circfirm-4.0.1}/tests/cli/test_cli_update.py +53 -20
  40. circfirm-4.0.1/tests/conftest.py +169 -0
  41. circfirm-4.0.1/tests/helpers.py +96 -0
  42. {circfirm-3.1.0 → circfirm-4.0.1}/tests/test_startup.py +0 -1
  43. circfirm-3.1.0/VERSION +0 -1
  44. circfirm-3.1.0/circfirm.egg-info/requires.txt +0 -17
  45. circfirm-3.1.0/tests/conftest.py +0 -53
  46. circfirm-3.1.0/tests/helpers.py +0 -191
  47. {circfirm-3.1.0 → circfirm-4.0.1}/.github/workflows/codeql.yml +0 -0
  48. {circfirm-3.1.0 → circfirm-4.0.1}/.github/workflows/publish.yml +0 -0
  49. {circfirm-3.1.0 → circfirm-4.0.1}/.gitignore +0 -0
  50. {circfirm-3.1.0 → circfirm-4.0.1}/.readthedocs.yaml +0 -0
  51. {circfirm-3.1.0 → circfirm-4.0.1}/.reuse/dep5 +0 -0
  52. {circfirm-3.1.0 → circfirm-4.0.1}/LICENSE +0 -0
  53. {circfirm-3.1.0 → circfirm-4.0.1}/LICENSES/MIT.txt +0 -0
  54. {circfirm-3.1.0 → circfirm-4.0.1}/LICENSES/Unlicense.txt +0 -0
  55. {circfirm-3.1.0 → circfirm-4.0.1}/NOTICE +0 -0
  56. {circfirm-3.1.0 → circfirm-4.0.1}/NOTICE.license +0 -0
  57. {circfirm-3.1.0 → circfirm-4.0.1}/README.rst +0 -0
  58. {circfirm-3.1.0 → circfirm-4.0.1}/VERSION.license +0 -0
  59. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/__init__.py +0 -0
  60. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/cli/about.py +0 -0
  61. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/cli/config.py +0 -0
  62. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/py.typed +0 -0
  63. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/templates/settings.yaml +0 -0
  64. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm/templates/settings.yaml.license +0 -0
  65. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm.egg-info/dependency_links.txt +0 -0
  66. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm.egg-info/entry_points.txt +0 -0
  67. {circfirm-3.1.0 → circfirm-4.0.1}/circfirm.egg-info/top_level.txt +0 -0
  68. {circfirm-3.1.0 → circfirm-4.0.1}/docs/commands/cache.rst +0 -0
  69. {circfirm-3.1.0 → circfirm-4.0.1}/docs/commands/config.rst +0 -0
  70. {circfirm-3.1.0 → circfirm-4.0.1}/docs/commands/current.rst +0 -0
  71. {circfirm-3.1.0 → circfirm-4.0.1}/docs/commands/query.rst +0 -0
  72. {circfirm-3.1.0 → circfirm-4.0.1}/docs/examples/update_many.rst +0 -0
  73. {circfirm-3.1.0 → circfirm-4.0.1}/docs/examples/weekly_cache.rst +0 -0
  74. {circfirm-3.1.0 → circfirm-4.0.1}/docs/index.rst.license +0 -0
  75. {circfirm-3.1.0 → circfirm-4.0.1}/docs/infrastructure/ci.rst +0 -0
  76. {circfirm-3.1.0 → circfirm-4.0.1}/docs/infrastructure/codecheck.rst +0 -0
  77. {circfirm-3.1.0 → circfirm-4.0.1}/docs/infrastructure/docs.rst +0 -0
  78. {circfirm-3.1.0 → circfirm-4.0.1}/docs/infrastructure/license.rst +0 -0
  79. {circfirm-3.1.0 → circfirm-4.0.1}/docs/infrastructure/notice.rst +0 -0
  80. {circfirm-3.1.0 → circfirm-4.0.1}/docs/infrastructure/tests.rst +0 -0
  81. {circfirm-3.1.0 → circfirm-4.0.1}/examples/bash/update_many.sh +0 -0
  82. {circfirm-3.1.0 → circfirm-4.0.1}/examples/bash/weekly_cache.sh +0 -0
  83. {circfirm-3.1.0 → circfirm-4.0.1}/examples/powershell/update_many.ps1 +0 -0
  84. {circfirm-3.1.0 → circfirm-4.0.1}/examples/powershell/weekly_cache.ps1 +0 -0
  85. {circfirm-3.1.0 → circfirm-4.0.1}/setup.cfg +0 -0
  86. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/boot_out.txt +0 -0
  87. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-en_US-7.0.0.uf2 +0 -0
  88. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-en_US-7.1.0.uf2 +0 -0
  89. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-en_US-7.2.0.uf2 +0 -0
  90. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-fr-7.0.0.uf2 +0 -0
  91. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-fr-7.1.0.uf2 +0 -0
  92. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-fr-7.2.0.uf2 +0 -0
  93. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-zh_Latn_pinyin-7.0.0.uf2 +0 -0
  94. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-zh_Latn_pinyin-7.1.0.uf2 +0 -0
  95. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-zh_Latn_pinyin-7.2.0.uf2 +0 -0
  96. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-en_US-7.0.0.uf2 +0 -0
  97. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-en_US-7.1.0.uf2 +0 -0
  98. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-en_US-7.2.0.uf2 +0 -0
  99. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-fr-7.0.0.uf2 +0 -0
  100. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-fr-7.1.0.uf2 +0 -0
  101. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-fr-7.2.0.uf2 +0 -0
  102. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-zh_Latn_pinyin-7.0.0.uf2 +0 -0
  103. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-zh_Latn_pinyin-7.1.0.uf2 +0 -0
  104. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-zh_Latn_pinyin-7.2.0.uf2 +0 -0
  105. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-en_US-7.0.0.uf2 +0 -0
  106. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-en_US-7.1.0.uf2 +0 -0
  107. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-en_US-7.2.0.uf2 +0 -0
  108. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-fr-7.0.0.uf2 +0 -0
  109. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-fr-7.1.0.uf2 +0 -0
  110. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-fr-7.2.0.uf2 +0 -0
  111. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-zh_Latn_pinyin-7.0.0.uf2 +0 -0
  112. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-zh_Latn_pinyin-7.1.0.uf2 +0 -0
  113. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-zh_Latn_pinyin-7.2.0.uf2 +0 -0
  114. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/info_uf2.txt +0 -0
  115. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/responses/full_list.txt +0 -0
  116. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/responses/full_list.txt.license +0 -0
  117. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/responses/specific_board.txt +0 -0
  118. {circfirm-3.1.0 → circfirm-4.0.1}/tests/assets/responses/specific_board.txt.license +0 -0
  119. {circfirm-3.1.0 → circfirm-4.0.1}/tests/backend/test_backend_cache.py +0 -0
  120. {circfirm-3.1.0 → circfirm-4.0.1}/tests/backend/test_backend_github.py +0 -0
  121. {circfirm-3.1.0 → circfirm-4.0.1}/tests/cli/test_cli_about.py +0 -0
  122. {circfirm-3.1.0 → circfirm-4.0.1}/tests/cli/test_cli_config.py +0 -0
@@ -16,11 +16,11 @@ jobs:
16
16
  fail-fast: false
17
17
  matrix:
18
18
  py-version: [
19
- "3.8",
20
19
  "3.9",
21
20
  "3.10",
22
21
  "3.11",
23
22
  "3.12",
23
+ "3.13",
24
24
  ]
25
25
  os: [
26
26
  "ubuntu-latest",
@@ -28,22 +28,22 @@ jobs:
28
28
  "macos-latest",
29
29
  ]
30
30
  exclude:
31
- - os: windows-latest
32
- py-version: "3.9"
33
31
  - os: windows-latest
34
32
  py-version: "3.10"
35
33
  - os: windows-latest
36
34
  py-version: "3.11"
37
35
  - os: windows-latest
38
36
  py-version: "3.12"
39
- - os: macos-latest
40
- py-version: "3.9"
37
+ - os: windows-latest
38
+ py-version: "3.13"
41
39
  - os: macos-latest
42
40
  py-version: "3.10"
43
41
  - os: macos-latest
44
42
  py-version: "3.11"
45
43
  - os: macos-latest
46
44
  py-version: "3.12"
45
+ - os: macos-latest
46
+ py-version: "3.13"
47
47
 
48
48
  steps:
49
49
  - name: Setup Python 3.x
@@ -14,7 +14,7 @@ repos:
14
14
  - id: reuse
15
15
  name: Check REUSE compatibility
16
16
  - repo: https://github.com/pre-commit/pre-commit-hooks
17
- rev: v4.5.0
17
+ rev: v5.0.0
18
18
  hooks:
19
19
  - id: check-yaml
20
20
  name: Check YAML
@@ -59,8 +59,8 @@ test-run:
59
59
  test-clean:
60
60
  ifeq "$(OS)" "Windows_NT"
61
61
  -@subst T: /d
62
- -@python scripts/rmdir.py testmount
63
- -@python scripts/rmdir.py tests/sandbox/circuitpython
62
+ -@python scripts\rmdir.py testmount
63
+ -@python scripts\rmdir.py tests\sandbox\circuitpython
64
64
  else ifeq "$(shell uname -s)" "Linux"
65
65
  -@sudo umount testmount
66
66
  -@sudo rm -rf testmount
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: circfirm
3
- Version: 3.1.0
3
+ Version: 4.0.1
4
4
  Summary: CLI tool for install firmware for CircuitPython boards
5
5
  Author-email: Alec Delaney <tekktrik@gmail.com>
6
6
  License: MIT
@@ -15,37 +15,38 @@ Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Development Status :: 5 - Production/Stable
16
16
  Classifier: Environment :: Console
17
17
  Classifier: Natural Language :: English
18
- Classifier: Programming Language :: Python :: 3.8
19
18
  Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
23
  Classifier: Programming Language :: Python :: Implementation :: CPython
24
24
  Classifier: Operating System :: Unix
25
25
  Classifier: Operating System :: Microsoft :: Windows
26
26
  Classifier: Operating System :: MacOS
27
27
  Classifier: Typing :: Typed
28
- Requires-Python: >=3.8.0
28
+ Requires-Python: >=3.9.0
29
29
  Description-Content-Type: text/x-rst
30
30
  License-File: LICENSE
31
31
  License-File: NOTICE
32
32
  License-File: NOTICE.license
33
- Requires-Dist: boto3~=1.34
34
- Requires-Dist: click~=8.0
33
+ Requires-Dist: boto3~=1.35
34
+ Requires-Dist: click~=8.1
35
35
  Requires-Dist: click-spinner~=0.1
36
- Requires-Dist: packaging~=23.2
37
- Requires-Dist: psutil~=5.9
36
+ Requires-Dist: packaging~=24.2
37
+ Requires-Dist: psutil~=6.1
38
38
  Requires-Dist: pyyaml~=6.0
39
- Requires-Dist: requests~=2.31
40
- Requires-Dist: boto3-stubs[essential]~=1.34
39
+ Requires-Dist: requests~=2.32
40
+ Requires-Dist: boto3-stubs[essential]~=1.35
41
41
  Provides-Extra: dev
42
- Requires-Dist: build~=1.0; extra == "dev"
43
- Requires-Dist: coverage~=7.4; extra == "dev"
44
- Requires-Dist: pre-commit~=2.20; extra == "dev"
45
- Requires-Dist: pytest~=8.0; extra == "dev"
46
- Requires-Dist: sphinx~=5.1; extra == "dev"
42
+ Requires-Dist: build~=1.2; extra == "dev"
43
+ Requires-Dist: coverage~=7.6; extra == "dev"
44
+ Requires-Dist: pre-commit~=4.0; extra == "dev"
45
+ Requires-Dist: pytest~=8.3; extra == "dev"
46
+ Requires-Dist: sphinx~=7.4; extra == "dev"
47
47
  Requires-Dist: sphinx-tabs~=3.4; extra == "dev"
48
- Requires-Dist: sphinx-rtd-theme~=1.0; extra == "dev"
48
+ Requires-Dist: sphinx-rtd-theme~=3.0; extra == "dev"
49
+ Dynamic: license-file
49
50
 
50
51
  ..
51
52
  SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries
circfirm-4.0.1/VERSION ADDED
@@ -0,0 +1 @@
1
+ 4.0.1
@@ -9,7 +9,6 @@ Author(s): Alec Delaney
9
9
 
10
10
  import enum
11
11
  import re
12
- from typing import Tuple
13
12
 
14
13
 
15
14
  class Language(enum.Enum):
@@ -60,7 +59,7 @@ def get_uf2_filename(board_id: str, version: str, language: str = "en_US") -> st
60
59
  return f"adafruit-circuitpython-{board_id}-{language}-{version}.uf2"
61
60
 
62
61
 
63
- def parse_firmware_info(uf2_filename: str) -> Tuple[str, str]:
62
+ def parse_firmware_info(uf2_filename: str) -> tuple[str, str]:
64
63
  """Get firmware info."""
65
64
  regex_match = re.match(FIRMWARE_REGEX, uf2_filename)
66
65
  if regex_match is None:
@@ -9,14 +9,12 @@ Author(s): Alec Delaney
9
9
 
10
10
  import os
11
11
  import pathlib
12
- import re
13
- from typing import Dict, List, Optional, Set, Tuple
12
+ from typing import Optional
14
13
 
15
14
  import packaging.version
16
15
  import requests
17
16
 
18
17
  import circfirm.backend
19
- import circfirm.startup
20
18
 
21
19
 
22
20
  def get_uf2_filepath(
@@ -48,19 +46,21 @@ def download_uf2(board_id: str, version: str, language: str = "en_US") -> None:
48
46
 
49
47
  SUCCESS = 200
50
48
  if response.status_code != SUCCESS:
51
- raise ConnectionError(f"Could not download the specified UF2 file:\n{url}")
49
+ raise ConnectionError(
50
+ f"Could not download the specified UF2 file:\n{url}\nAre the board ID, version, and language correct?"
51
+ )
52
52
 
53
53
  uf2_file.parent.mkdir(parents=True, exist_ok=True)
54
54
  with open(uf2_file, mode="wb") as uf2file:
55
55
  uf2file.write(response.content)
56
56
 
57
57
 
58
- def get_sorted_boards(board_id: Optional[str]) -> Dict[str, Dict[str, Set[str]]]:
58
+ def get_sorted_boards(board_id: Optional[str]) -> dict[str, dict[str, set[str]]]:
59
59
  """Get a sorted collection of boards, versions, and languages."""
60
- boards: Dict[str, Dict[str, Set[str]]] = {}
60
+ boards: dict[str, dict[str, set[str]]] = {}
61
61
  for board_folder in sorted(os.listdir(circfirm.UF2_ARCHIVE)):
62
- versions: Dict[str, List[str]] = {}
63
- sorted_versions: Dict[str, Set[str]] = {}
62
+ versions: dict[str, list[str]] = {}
63
+ sorted_versions: dict[str, set[str]] = {}
64
64
  if board_id is not None and board_id != board_folder:
65
65
  continue
66
66
  board_folder_full = get_board_folder(board_folder)
@@ -9,7 +9,7 @@ Author(s): Alec Delaney
9
9
 
10
10
  import pathlib
11
11
  import re
12
- from typing import Optional, Tuple
12
+ from typing import Optional
13
13
 
14
14
  import psutil
15
15
 
@@ -21,7 +21,7 @@ BOARD_VER_REGEX = (
21
21
  )
22
22
 
23
23
 
24
- def get_board_info(device_path: str) -> Tuple[str, str]:
24
+ def get_board_info(device_path: str) -> tuple[str, str]:
25
25
  """Get the attached CircuitPytho board's name and version."""
26
26
  bootout_file = pathlib.Path(device_path) / circfirm.BOOTOUT_FILE
27
27
  with open(bootout_file, encoding="utf-8") as infofile:
@@ -9,7 +9,7 @@ Author(s): Alec Delaney
9
9
 
10
10
  import datetime
11
11
  import re
12
- from typing import List, Tuple, TypedDict
12
+ from typing import TypedDict
13
13
 
14
14
  import requests
15
15
 
@@ -42,7 +42,7 @@ class GitTreeItem(TypedDict):
42
42
  url: str
43
43
 
44
44
 
45
- def get_rate_limit() -> Tuple[int, int, datetime.datetime]:
45
+ def get_rate_limit() -> tuple[int, int, datetime.datetime]:
46
46
  """Get the rate limit for the GitHub REST endpoint."""
47
47
  response = requests.get(
48
48
  url="https://api.github.com/rate_limit",
@@ -55,7 +55,7 @@ def get_rate_limit() -> Tuple[int, int, datetime.datetime]:
55
55
  return available, total, reset_time
56
56
 
57
57
 
58
- def get_board_id_list(token: str) -> List[str]:
58
+ def get_board_id_list(token: str) -> list[str]:
59
59
  """Get a list of CircuitPython boards."""
60
60
  boards = set()
61
61
  headers = BASE_REQUESTS_HEADERS.copy()
@@ -69,7 +69,7 @@ def get_board_id_list(token: str) -> List[str]:
69
69
  headers=headers,
70
70
  )
71
71
  try:
72
- tree_items: List[GitTreeItem] = response.json()["tree"]
72
+ tree_items: list[GitTreeItem] = response.json()["tree"]
73
73
  except KeyError as err:
74
74
  raise ValueError("Could not parse JSON response, check token") from err
75
75
  for tree_item in tree_items:
@@ -8,7 +8,7 @@ Author(s): Alec Delaney
8
8
  """
9
9
 
10
10
  import re
11
- from typing import List, Optional
11
+ from typing import Optional
12
12
 
13
13
  import boto3
14
14
  import botocore
@@ -17,7 +17,6 @@ import packaging.version
17
17
  from mypy_boto3_s3 import S3ServiceResource
18
18
 
19
19
  import circfirm.backend
20
- import circfirm.backend.cache
21
20
 
22
21
  S3_CONFIG = botocore.client.Config(signature_version=botocore.UNSIGNED)
23
22
  S3_RESOURCE: S3ServiceResource = boto3.resource("s3", config=S3_CONFIG)
@@ -27,7 +26,7 @@ BUCKET = S3_RESOURCE.Bucket(BUCKET_NAME)
27
26
 
28
27
  def get_board_versions(
29
28
  board_id: str, language: str = "en_US", *, regex: Optional[str] = None
30
- ) -> List[str]:
29
+ ) -> list[str]:
31
30
  """Get a list of CircuitPython versions for a given board."""
32
31
  prefix = f"bin/{board_id}/{language}"
33
32
  firmware_regex = circfirm.backend.FIRMWARE_REGEX_PATTERN.replace(
@@ -13,7 +13,8 @@ import pkgutil
13
13
  import shutil
14
14
  import sys
15
15
  import time
16
- from typing import Any, Callable, Dict, Iterable, Optional, Tuple, TypeVar
16
+ from collections.abc import Iterable
17
+ from typing import Any, Callable, Optional, TypeVar
17
18
 
18
19
  import click
19
20
  import click_spinner
@@ -43,8 +44,11 @@ def maybe_support(msg: str) -> None:
43
44
 
44
45
 
45
46
  def get_board_id(
46
- circuitpy: Optional[str], bootloader: Optional[str], board: Optional[str]
47
- ) -> Tuple[str, str]:
47
+ circuitpy: Optional[str],
48
+ bootloader: Optional[str],
49
+ board: Optional[str],
50
+ timeout: int = -1,
51
+ ) -> tuple[str, str]:
48
52
  """Get the board ID of a device via CLI."""
49
53
  if not board:
50
54
  if not circuitpy and bootloader:
@@ -56,12 +60,22 @@ def get_board_id(
56
60
  board = circfirm.backend.device.get_board_info(circuitpy)[0]
57
61
 
58
62
  click.echo("Board ID detected, please switch the device to bootloader mode.")
63
+ if timeout == -1:
64
+ skip_timeout = True
65
+ else:
66
+ skip_timeout = False
67
+ start_time = time.time()
68
+
59
69
  while not (bootloader := circfirm.backend.device.find_bootloader()):
60
- time.sleep(1)
70
+ if not skip_timeout and time.time() >= start_time + timeout:
71
+ raise OSError(
72
+ "Bootloader mode device not found within the timeout period"
73
+ )
74
+ time.sleep(0.05)
61
75
  return bootloader, board
62
76
 
63
77
 
64
- def get_connection_status() -> Tuple[Optional[str], Optional[str]]:
78
+ def get_connection_status() -> tuple[Optional[str], Optional[str]]:
65
79
  """Get the status of a connectted CircuitPython device as a CIRCUITPY and bootloader location."""
66
80
  circuitpy = circfirm.backend.device.find_circuitpy()
67
81
  bootloader = circfirm.backend.device.find_bootloader()
@@ -116,7 +130,7 @@ def announce_and_await(
116
130
  msg: str,
117
131
  func: Callable[..., _T],
118
132
  args: Iterable = (),
119
- kwargs: Optional[Dict[str, Any]] = None,
133
+ kwargs: Optional[dict[str, Any]] = None,
120
134
  *,
121
135
  use_spinner: bool = True,
122
136
  ) -> _T:
@@ -141,7 +155,7 @@ def announce_and_await(
141
155
  raise err
142
156
 
143
157
 
144
- def get_settings() -> Dict[str, Any]:
158
+ def get_settings() -> dict[str, Any]:
145
159
  """Get the contents of the settings file."""
146
160
  with open(circfirm.SETTINGS_FILE, encoding="utf-8") as yamlfile:
147
161
  return yaml.safe_load(yamlfile)
@@ -13,6 +13,7 @@ import re
13
13
  import shutil
14
14
  from typing import Optional
15
15
 
16
+ import botocore.exceptions
16
17
  import click
17
18
 
18
19
  import circfirm
@@ -153,3 +154,7 @@ def cache_latest(board_id: str, language: str, pre_release: bool) -> None:
153
154
  )
154
155
  except ConnectionError as err:
155
156
  raise click.exceptions.ClickException(err.args[0])
157
+ except botocore.exceptions.ConnectionError:
158
+ raise click.exceptions.ClickException(
159
+ "Could not connect to the S3 bucket - check network connection"
160
+ )
@@ -7,15 +7,13 @@
7
7
  Author(s): Alec Delaney
8
8
  """
9
9
 
10
- from typing import Tuple
11
-
12
10
  import click
13
11
 
14
12
  import circfirm.backend.device
15
13
  import circfirm.cli
16
14
 
17
15
 
18
- def get_board_info() -> Tuple[str, str]:
16
+ def get_board_info() -> tuple[str, str]:
19
17
  """Get board info via the CLI."""
20
18
  circuitpy, _ = circfirm.cli.get_connection_status()
21
19
  if not circuitpy:
@@ -0,0 +1,38 @@
1
+ # SPDX-FileCopyrightText: 2024 Alec Delaney, for Adafruit Industries
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """CLI functionality for the detect subcommand.
6
+
7
+ Author(s): Alec Delaney
8
+ """
9
+
10
+
11
+ import click
12
+
13
+ import circfirm.backend.device
14
+
15
+
16
+ @click.group()
17
+ def cli() -> None:
18
+ """Detect connected CircuitPython boards."""
19
+
20
+
21
+ @cli.command(name="circuitpy")
22
+ def detect_circuitpy() -> None:
23
+ """Detect a connected board in CIRCUITPY or equivalent mode."""
24
+ circuitpy = circfirm.backend.device.find_circuitpy()
25
+ if not circuitpy:
26
+ click.echo("No board connected in CIRCUITPY or equivalent mode")
27
+ return
28
+ click.echo(circuitpy)
29
+
30
+
31
+ @cli.command(name="bootloader")
32
+ def detect_bootloader() -> None:
33
+ """Detect a connected board in bootloader mode."""
34
+ bootloader = circfirm.backend.device.find_bootloader()
35
+ if not bootloader:
36
+ click.echo("No board connected in bootloader mode")
37
+ return
38
+ click.echo(bootloader)
@@ -23,10 +23,21 @@ import circfirm.cli
23
23
  default=None,
24
24
  help="Assume the given board ID (and connect in bootloader mode)",
25
25
  )
26
- def cli(version: str, language: str, board_id: Optional[str]) -> None:
26
+ @click.option(
27
+ "-t",
28
+ "--timeout",
29
+ default=-1,
30
+ help="Set a timeout in seconds for the switch to bootloader mode",
31
+ )
32
+ def cli(version: str, language: str, board_id: Optional[str], timeout: int) -> None:
27
33
  """Install the specified version of CircuitPython."""
28
34
  circuitpy, bootloader = circfirm.cli.get_connection_status()
29
- bootloader, board_id = circfirm.cli.get_board_id(circuitpy, bootloader, board_id)
35
+ try:
36
+ bootloader, board_id = circfirm.cli.get_board_id(
37
+ circuitpy, bootloader, board_id, timeout
38
+ )
39
+ except OSError as err:
40
+ raise click.ClickException(err.args[0])
30
41
  circfirm.cli.ensure_bootloader_mode(bootloader)
31
42
  circfirm.cli.download_if_needed(board_id, version, language)
32
43
  circfirm.cli.copy_cache_firmware(board_id, version, language, bootloader)
@@ -9,6 +9,7 @@ Author(s): Alec Delaney
9
9
 
10
10
  import re
11
11
 
12
+ import botocore.exceptions
12
13
  import click
13
14
  import requests
14
15
 
@@ -51,12 +52,18 @@ def query_board_ids(regex: str) -> None:
51
52
  except ValueError as err:
52
53
  raise click.ClickException(err.args[0])
53
54
  except requests.ConnectionError as err:
55
+ print("Triggered!")
54
56
  raise click.ClickException(
55
57
  "Issue with requesting information from git repository, check network connection"
56
58
  )
57
59
  for board in boards:
58
60
  board_id = board.strip()
59
- result = re.search(regex, board_id)
61
+ try:
62
+ result = re.search(regex, board_id)
63
+ except re.PatternError:
64
+ raise click.exceptions.ClickException(
65
+ "Regex pattern error - please check the regex syntax"
66
+ )
60
67
  if result:
61
68
  click.echo(board_id)
62
69
 
@@ -69,7 +76,16 @@ def query_board_ids(regex: str) -> None:
69
76
  )
70
77
  def query_versions(board_id: str, language: str, regex: str) -> None:
71
78
  """Query the CircuitPython versions available for a board."""
72
- versions = circfirm.backend.s3.get_board_versions(board_id, language, regex=regex)
79
+ try:
80
+ versions = circfirm.backend.s3.get_board_versions(
81
+ board_id, language, regex=regex
82
+ )
83
+ except botocore.exceptions.ConnectionError as err:
84
+ raise click.exceptions.ClickException(err.args[0])
85
+ except re.PatternError:
86
+ raise click.exceptions.ClickException(
87
+ "Regex pattern error - please check the regex syntax"
88
+ )
73
89
  for version in reversed(versions):
74
90
  click.echo(version)
75
91
 
@@ -86,8 +102,11 @@ def query_versions(board_id: str, language: str, regex: str) -> None:
86
102
  )
87
103
  def query_latest(board_id: str, language: str, pre_release: bool) -> None:
88
104
  """Query the latest CircuitPython versions available."""
89
- version = circfirm.backend.s3.get_latest_board_version(
90
- board_id, language, pre_release
91
- )
105
+ try:
106
+ version = circfirm.backend.s3.get_latest_board_version(
107
+ board_id, language, pre_release
108
+ )
109
+ except botocore.exceptions.ConnectionError as err:
110
+ raise click.exceptions.ClickException(err.args[0])
92
111
  if version:
93
112
  click.echo(version)
@@ -9,12 +9,12 @@ Author(s): Alec Delaney
9
9
 
10
10
  from typing import Optional
11
11
 
12
+ import botocore.exceptions
12
13
  import click
13
14
  import packaging.version
14
15
 
15
16
  import circfirm.backend.device
16
17
  import circfirm.backend.s3
17
- import circfirm.cli.install
18
18
 
19
19
 
20
20
  @click.command()
@@ -25,6 +25,12 @@ import circfirm.cli.install
25
25
  help="Assume the given board ID (and connect in bootloader mode)",
26
26
  )
27
27
  @click.option("-l", "--language", default="en_US", help="CircuitPython langauge/locale")
28
+ @click.option(
29
+ "-t",
30
+ "--timeout",
31
+ default=-1,
32
+ help="Set a timeout in seconds for the switch to bootloader mode",
33
+ )
28
34
  @click.option(
29
35
  "-p",
30
36
  "--pre-release",
@@ -46,9 +52,10 @@ import circfirm.cli.install
46
52
  default=False,
47
53
  help="Upgrade up to patch version updates",
48
54
  )
49
- def cli(
55
+ def cli( # noqa: PLR0913
50
56
  board_id: Optional[str],
51
57
  language: str,
58
+ timeout: int,
52
59
  pre_release: bool,
53
60
  limit_to_minor: bool,
54
61
  limit_to_patch: bool,
@@ -65,9 +72,17 @@ def cli(
65
72
  "The latest version will be installed regardless of the currently installed version."
66
73
  )
67
74
  current_version = "0.0.0"
68
- bootloader, board_id = circfirm.cli.get_board_id(circuitpy, bootloader, board_id)
75
+ try:
76
+ bootloader, board_id = circfirm.cli.get_board_id(
77
+ circuitpy, bootloader, board_id, timeout
78
+ )
79
+ except OSError as err:
80
+ raise click.ClickException(err.args[0])
69
81
 
70
- new_versions = circfirm.backend.s3.get_board_versions(board_id, language)
82
+ try:
83
+ new_versions = circfirm.backend.s3.get_board_versions(board_id, language)
84
+ except botocore.exceptions.ConnectionError as err:
85
+ raise click.exceptions.ClickException(err.args[0])
71
86
 
72
87
  if not pre_release:
73
88
  new_versions = [
@@ -10,13 +10,12 @@ Author(s): Alec Delaney
10
10
  import os
11
11
  import pathlib
12
12
  import shutil
13
- from typing import List, Tuple
14
13
 
15
14
  import click
16
15
 
17
- FOLDER_LIST: List[str] = []
18
- FILE_LIST: List[str] = []
19
- TEMPLATE_LIST: List[Tuple[str, str]] = []
16
+ FOLDER_LIST: list[str] = []
17
+ FILE_LIST: list[str] = []
18
+ TEMPLATE_LIST: list[tuple[str, str]] = []
20
19
 
21
20
 
22
21
  def specify_app_dir(app_name: str) -> str:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: circfirm
3
- Version: 3.1.0
3
+ Version: 4.0.1
4
4
  Summary: CLI tool for install firmware for CircuitPython boards
5
5
  Author-email: Alec Delaney <tekktrik@gmail.com>
6
6
  License: MIT
@@ -15,37 +15,38 @@ Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Development Status :: 5 - Production/Stable
16
16
  Classifier: Environment :: Console
17
17
  Classifier: Natural Language :: English
18
- Classifier: Programming Language :: Python :: 3.8
19
18
  Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
23
  Classifier: Programming Language :: Python :: Implementation :: CPython
24
24
  Classifier: Operating System :: Unix
25
25
  Classifier: Operating System :: Microsoft :: Windows
26
26
  Classifier: Operating System :: MacOS
27
27
  Classifier: Typing :: Typed
28
- Requires-Python: >=3.8.0
28
+ Requires-Python: >=3.9.0
29
29
  Description-Content-Type: text/x-rst
30
30
  License-File: LICENSE
31
31
  License-File: NOTICE
32
32
  License-File: NOTICE.license
33
- Requires-Dist: boto3~=1.34
34
- Requires-Dist: click~=8.0
33
+ Requires-Dist: boto3~=1.35
34
+ Requires-Dist: click~=8.1
35
35
  Requires-Dist: click-spinner~=0.1
36
- Requires-Dist: packaging~=23.2
37
- Requires-Dist: psutil~=5.9
36
+ Requires-Dist: packaging~=24.2
37
+ Requires-Dist: psutil~=6.1
38
38
  Requires-Dist: pyyaml~=6.0
39
- Requires-Dist: requests~=2.31
40
- Requires-Dist: boto3-stubs[essential]~=1.34
39
+ Requires-Dist: requests~=2.32
40
+ Requires-Dist: boto3-stubs[essential]~=1.35
41
41
  Provides-Extra: dev
42
- Requires-Dist: build~=1.0; extra == "dev"
43
- Requires-Dist: coverage~=7.4; extra == "dev"
44
- Requires-Dist: pre-commit~=2.20; extra == "dev"
45
- Requires-Dist: pytest~=8.0; extra == "dev"
46
- Requires-Dist: sphinx~=5.1; extra == "dev"
42
+ Requires-Dist: build~=1.2; extra == "dev"
43
+ Requires-Dist: coverage~=7.6; extra == "dev"
44
+ Requires-Dist: pre-commit~=4.0; extra == "dev"
45
+ Requires-Dist: pytest~=8.3; extra == "dev"
46
+ Requires-Dist: sphinx~=7.4; extra == "dev"
47
47
  Requires-Dist: sphinx-tabs~=3.4; extra == "dev"
48
- Requires-Dist: sphinx-rtd-theme~=1.0; extra == "dev"
48
+ Requires-Dist: sphinx-rtd-theme~=3.0; extra == "dev"
49
+ Dynamic: license-file
49
50
 
50
51
  ..
51
52
  SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries
@@ -36,6 +36,7 @@ circfirm/cli/about.py
36
36
  circfirm/cli/cache.py
37
37
  circfirm/cli/config.py
38
38
  circfirm/cli/current.py
39
+ circfirm/cli/detect.py
39
40
  circfirm/cli/install.py
40
41
  circfirm/cli/query.py
41
42
  circfirm/cli/update.py
@@ -47,6 +48,7 @@ docs/index.rst.license
47
48
  docs/commands/cache.rst
48
49
  docs/commands/config.rst
49
50
  docs/commands/current.rst
51
+ docs/commands/detect.rst
50
52
  docs/commands/install.rst
51
53
  docs/commands/query.rst
52
54
  docs/commands/update.rst
@@ -108,6 +110,7 @@ tests/cli/test_cli_about.py
108
110
  tests/cli/test_cli_cache.py
109
111
  tests/cli/test_cli_config.py
110
112
  tests/cli/test_cli_current.py
113
+ tests/cli/test_cli_detect.py
111
114
  tests/cli/test_cli_install.py
112
115
  tests/cli/test_cli_query.py
113
116
  tests/cli/test_cli_update.py