circfirm 3.0.0__tar.gz → 4.0.0__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 (124) hide show
  1. {circfirm-3.0.0 → circfirm-4.0.0}/.github/workflows/push.yml +22 -5
  2. {circfirm-3.0.0 → circfirm-4.0.0}/.pre-commit-config.yaml +3 -3
  3. {circfirm-3.0.0 → circfirm-4.0.0}/Makefile +2 -2
  4. circfirm-4.0.0/NOTICE +64 -0
  5. circfirm-4.0.0/NOTICE.license +2 -0
  6. {circfirm-3.0.0 → circfirm-4.0.0}/PKG-INFO +18 -16
  7. circfirm-4.0.0/VERSION +1 -0
  8. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/backend/__init__.py +18 -0
  9. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/backend/cache.py +11 -27
  10. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/backend/device.py +2 -2
  11. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/backend/github.py +4 -4
  12. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/backend/s3.py +10 -8
  13. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/cli/__init__.py +23 -8
  14. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/cli/cache.py +65 -9
  15. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/cli/current.py +1 -3
  16. circfirm-4.0.0/circfirm/cli/detect.py +38 -0
  17. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/cli/install.py +13 -2
  18. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/cli/query.py +7 -3
  19. circfirm-4.0.0/circfirm/cli/update.py +122 -0
  20. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/startup.py +3 -4
  21. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm.egg-info/PKG-INFO +18 -16
  22. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm.egg-info/SOURCES.txt +7 -0
  23. circfirm-4.0.0/circfirm.egg-info/requires.txt +17 -0
  24. circfirm-4.0.0/docs/commands/cache.rst +74 -0
  25. circfirm-4.0.0/docs/commands/detect.rst +30 -0
  26. {circfirm-3.0.0 → circfirm-4.0.0}/docs/commands/install.rst +8 -0
  27. {circfirm-3.0.0 → circfirm-4.0.0}/docs/commands/update.rst +12 -0
  28. {circfirm-3.0.0 → circfirm-4.0.0}/docs/conf.py +0 -1
  29. {circfirm-3.0.0 → circfirm-4.0.0}/docs/index.rst +2 -0
  30. circfirm-4.0.0/docs/infrastructure/notice.rst +8 -0
  31. {circfirm-3.0.0 → circfirm-4.0.0}/pyproject.toml +4 -3
  32. {circfirm-3.0.0 → circfirm-4.0.0}/requirements-dev.txt +6 -6
  33. {circfirm-3.0.0 → circfirm-4.0.0}/requirements.txt +6 -6
  34. {circfirm-3.0.0 → circfirm-4.0.0}/scripts/rmdir.py +8 -0
  35. circfirm-4.0.0/tests/backend/test_backend.py +41 -0
  36. {circfirm-3.0.0 → circfirm-4.0.0}/tests/backend/test_backend_cache.py +0 -27
  37. {circfirm-3.0.0 → circfirm-4.0.0}/tests/backend/test_backend_github.py +0 -2
  38. circfirm-4.0.0/tests/backend/test_backend_s3.py +64 -0
  39. circfirm-4.0.0/tests/cli/test_cli_cache.py +282 -0
  40. circfirm-4.0.0/tests/cli/test_cli_detect.py +56 -0
  41. {circfirm-3.0.0 → circfirm-4.0.0}/tests/cli/test_cli_install.py +36 -1
  42. {circfirm-3.0.0 → circfirm-4.0.0}/tests/cli/test_cli_query.py +26 -12
  43. {circfirm-3.0.0 → circfirm-4.0.0}/tests/cli/test_cli_update.py +66 -3
  44. {circfirm-3.0.0 → circfirm-4.0.0}/tests/helpers.py +59 -11
  45. circfirm-3.0.0/VERSION +0 -1
  46. circfirm-3.0.0/circfirm/cli/update.py +0 -63
  47. circfirm-3.0.0/circfirm.egg-info/requires.txt +0 -17
  48. circfirm-3.0.0/docs/commands/cache.rst +0 -48
  49. circfirm-3.0.0/tests/backend/test_backend_s3.py +0 -26
  50. circfirm-3.0.0/tests/cli/test_cli_cache.py +0 -139
  51. {circfirm-3.0.0 → circfirm-4.0.0}/.github/workflows/codeql.yml +0 -0
  52. {circfirm-3.0.0 → circfirm-4.0.0}/.github/workflows/publish.yml +0 -0
  53. {circfirm-3.0.0 → circfirm-4.0.0}/.gitignore +0 -0
  54. {circfirm-3.0.0 → circfirm-4.0.0}/.readthedocs.yaml +0 -0
  55. {circfirm-3.0.0 → circfirm-4.0.0}/.reuse/dep5 +0 -0
  56. {circfirm-3.0.0 → circfirm-4.0.0}/LICENSE +0 -0
  57. {circfirm-3.0.0 → circfirm-4.0.0}/LICENSES/MIT.txt +0 -0
  58. {circfirm-3.0.0 → circfirm-4.0.0}/LICENSES/Unlicense.txt +0 -0
  59. {circfirm-3.0.0 → circfirm-4.0.0}/README.rst +0 -0
  60. {circfirm-3.0.0 → circfirm-4.0.0}/VERSION.license +0 -0
  61. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/__init__.py +0 -0
  62. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/cli/about.py +0 -0
  63. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/cli/config.py +0 -0
  64. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/py.typed +0 -0
  65. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/templates/settings.yaml +0 -0
  66. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm/templates/settings.yaml.license +0 -0
  67. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm.egg-info/dependency_links.txt +0 -0
  68. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm.egg-info/entry_points.txt +0 -0
  69. {circfirm-3.0.0 → circfirm-4.0.0}/circfirm.egg-info/top_level.txt +0 -0
  70. {circfirm-3.0.0 → circfirm-4.0.0}/docs/commands/config.rst +0 -0
  71. {circfirm-3.0.0 → circfirm-4.0.0}/docs/commands/current.rst +0 -0
  72. {circfirm-3.0.0 → circfirm-4.0.0}/docs/commands/query.rst +0 -0
  73. {circfirm-3.0.0 → circfirm-4.0.0}/docs/examples/update_many.rst +0 -0
  74. {circfirm-3.0.0 → circfirm-4.0.0}/docs/examples/weekly_cache.rst +0 -0
  75. {circfirm-3.0.0 → circfirm-4.0.0}/docs/index.rst.license +0 -0
  76. {circfirm-3.0.0 → circfirm-4.0.0}/docs/infrastructure/ci.rst +0 -0
  77. {circfirm-3.0.0 → circfirm-4.0.0}/docs/infrastructure/codecheck.rst +0 -0
  78. {circfirm-3.0.0 → circfirm-4.0.0}/docs/infrastructure/docs.rst +0 -0
  79. {circfirm-3.0.0 → circfirm-4.0.0}/docs/infrastructure/license.rst +0 -0
  80. {circfirm-3.0.0 → circfirm-4.0.0}/docs/infrastructure/tests.rst +0 -0
  81. {circfirm-3.0.0 → circfirm-4.0.0}/examples/bash/update_many.sh +0 -0
  82. {circfirm-3.0.0 → circfirm-4.0.0}/examples/bash/weekly_cache.sh +0 -0
  83. {circfirm-3.0.0 → circfirm-4.0.0}/examples/powershell/update_many.ps1 +0 -0
  84. {circfirm-3.0.0 → circfirm-4.0.0}/examples/powershell/weekly_cache.ps1 +0 -0
  85. {circfirm-3.0.0 → circfirm-4.0.0}/setup.cfg +0 -0
  86. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/boot_out.txt +0 -0
  87. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-en_US-7.0.0.uf2 +0 -0
  88. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-en_US-7.1.0.uf2 +0 -0
  89. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-en_US-7.2.0.uf2 +0 -0
  90. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-fr-7.0.0.uf2 +0 -0
  91. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-fr-7.1.0.uf2 +0 -0
  92. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-fr-7.2.0.uf2 +0 -0
  93. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-zh_Latn_pinyin-7.0.0.uf2 +0 -0
  94. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-zh_Latn_pinyin-7.1.0.uf2 +0 -0
  95. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m0_express/adafruit-circuitpython-feather_m0_express-zh_Latn_pinyin-7.2.0.uf2 +0 -0
  96. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-en_US-7.0.0.uf2 +0 -0
  97. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-en_US-7.1.0.uf2 +0 -0
  98. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-en_US-7.2.0.uf2 +0 -0
  99. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-fr-7.0.0.uf2 +0 -0
  100. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-fr-7.1.0.uf2 +0 -0
  101. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-fr-7.2.0.uf2 +0 -0
  102. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-zh_Latn_pinyin-7.0.0.uf2 +0 -0
  103. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-zh_Latn_pinyin-7.1.0.uf2 +0 -0
  104. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/feather_m4_express/adafruit-circuitpython-feather_m4_express-zh_Latn_pinyin-7.2.0.uf2 +0 -0
  105. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-en_US-7.0.0.uf2 +0 -0
  106. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-en_US-7.1.0.uf2 +0 -0
  107. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-en_US-7.2.0.uf2 +0 -0
  108. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-fr-7.0.0.uf2 +0 -0
  109. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-fr-7.1.0.uf2 +0 -0
  110. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-fr-7.2.0.uf2 +0 -0
  111. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-zh_Latn_pinyin-7.0.0.uf2 +0 -0
  112. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-zh_Latn_pinyin-7.1.0.uf2 +0 -0
  113. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/firmwares/pygamer/adafruit-circuitpython-pygamer-zh_Latn_pinyin-7.2.0.uf2 +0 -0
  114. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/info_uf2.txt +0 -0
  115. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/responses/full_list.txt +0 -0
  116. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/responses/full_list.txt.license +0 -0
  117. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/responses/specific_board.txt +0 -0
  118. {circfirm-3.0.0 → circfirm-4.0.0}/tests/assets/responses/specific_board.txt.license +0 -0
  119. {circfirm-3.0.0 → circfirm-4.0.0}/tests/backend/test_backend_device.py +0 -0
  120. {circfirm-3.0.0 → circfirm-4.0.0}/tests/cli/test_cli_about.py +0 -0
  121. {circfirm-3.0.0 → circfirm-4.0.0}/tests/cli/test_cli_config.py +0 -0
  122. {circfirm-3.0.0 → circfirm-4.0.0}/tests/cli/test_cli_current.py +0 -0
  123. {circfirm-3.0.0 → circfirm-4.0.0}/tests/conftest.py +0 -0
  124. {circfirm-3.0.0 → circfirm-4.0.0}/tests/test_startup.py +0 -0
@@ -14,20 +14,37 @@ jobs:
14
14
  runs-on: ${{ matrix.os }}
15
15
  strategy:
16
16
  fail-fast: false
17
- max-parallel: 3
18
17
  matrix:
19
18
  py-version: [
20
- "3.8",
21
19
  "3.9",
22
20
  "3.10",
23
21
  "3.11",
24
22
  "3.12",
23
+ "3.13",
25
24
  ]
26
25
  os: [
27
- ubuntu-latest,
28
- windows-latest,
29
- macos-latest
26
+ "ubuntu-latest",
27
+ "windows-latest",
28
+ "macos-latest",
30
29
  ]
30
+ exclude:
31
+ - os: windows-latest
32
+ py-version: "3.10"
33
+ - os: windows-latest
34
+ py-version: "3.11"
35
+ - os: windows-latest
36
+ py-version: "3.12"
37
+ - os: windows-latest
38
+ py-version: "3.13"
39
+ - os: macos-latest
40
+ py-version: "3.10"
41
+ - os: macos-latest
42
+ py-version: "3.11"
43
+ - os: macos-latest
44
+ py-version: "3.12"
45
+ - os: macos-latest
46
+ py-version: "3.13"
47
+
31
48
  steps:
32
49
  - name: Setup Python 3.x
33
50
  uses: actions/setup-python@v5
@@ -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
@@ -31,8 +31,8 @@ repos:
31
31
  - repo: https://github.com/astral-sh/ruff-pre-commit
32
32
  rev: v0.2.2
33
33
  hooks:
34
+ - id: ruff-format
35
+ name: Format via ruff
34
36
  - id: ruff
35
37
  name: Lint via ruff
36
38
  args: [--fix]
37
- - id: ruff-format
38
- name: Format via ruff
@@ -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
circfirm-4.0.0/NOTICE ADDED
@@ -0,0 +1,64 @@
1
+ Required Dependencies
2
+ ^^^^^^^^^^^^^^^^^^^^^
3
+
4
+ This software interacts with the CircuitPython AWS S3 bucket using boto3 by
5
+ Amazon.com Inc or its affiliates (https://github.com/boto/boto3) [Apache-2.0 license].
6
+
7
+ This software uses type annotations for boto3 provided by boto3-stubs by Vlad
8
+ Emelinaov and maintained by youtype (https://github.com/youtype/mypy_boto3_builder)
9
+ [MIT license].
10
+
11
+ This software creates its command line interface using click by the pallets
12
+ project (https://palletsprojects.com/p/click/) [BSD-3-Clause license].
13
+
14
+ This software uses a CLI spinner add-on click-spinner to clink to display loading
15
+ animation created by Yoav Ram (https://github.com/click-contrib/click-spinner)
16
+ [MIT license].
17
+
18
+ This software compares and orders CircuitPython versions according to PEP 440
19
+ using the packaging library by Donald Stufft and maintained by the Python Packaging
20
+ Authority (https://packaging.pypa.io/en/stable/) [Apache-2.0 or BSD-2-Clause
21
+ licenses].
22
+
23
+ This software discovers connected CircuitPython boards using the system utilization
24
+ features of psutil, created by Jay Loden, Dave Daeschler, and Giampaolo Rodola
25
+ (https://github.com/giampaolo/psutil) [BSD-3-Clause license].
26
+
27
+ This software reads and modifies the YAML configuration settings using PyYAML,
28
+ created by Kirill Simonov and Ingy dot Net, and maintained by the YAML community
29
+ (https://pyyaml.org/) [MIT license].
30
+
31
+ requests~=2.31
32
+ This software makes HTTP requests, such as communicating with the CircuitPython
33
+ GitHub repository and downloading CircuitPython firmware files, using requests,
34
+ created by Kenneth Reitz and maintained by the Python Software Foundation
35
+ (https://requests.readthedocs.io/en/latest/) [Apache-2.0 license].
36
+
37
+
38
+ Development Dependencies
39
+ ^^^^^^^^^^^^^^^^^^^^^^^^
40
+
41
+ This software uses the build package for its build frontend, created by Filipe Lains
42
+ and maintained by the Python Packaging Authority (https://build.pypa.io/en/stable/)
43
+ [MIT license].
44
+
45
+ This software measures code coverage metrics using Coverage.py, authored by and
46
+ copyright Ned Batchelder (https://github.com/nedbat/coveragepy) [Apache-2.0 license].
47
+
48
+ This software manages code checks such as linting and formatting using pre-commit,
49
+ copyright Anythony Sottile and Ken Struys and (https://github.com/pre-commit/pre-commit)
50
+ [MIT license].
51
+
52
+ This software performs software tests using pytest, copyright Holger Krekel and others
53
+ (https://docs.pytest.org/en/latest/) [MIT license].
54
+
55
+ Ths software creates its documentation using Sphinx, copyright the Sphinx team
56
+ (https://www.sphinx-doc.org/en/master/) [BSD license].
57
+
58
+ This software uses tabs within its documentation provided by sphinx-tabs, copyright
59
+ djungelorm (https://github.com/executablebooks/sphinx-tabs) [MIT license].
60
+
61
+ sphinx-rtd-theme~=1.0
62
+ This software displays its documentation using the ReadTheDocs Sphinx theme, copyright Dave
63
+ Snider, Read the Docs, Inc. & contributors (https://sphinx-rtd-theme.readthedocs.io/en/stable/)
64
+ [MIT license].
@@ -0,0 +1,2 @@
1
+ SPDX-FileCopyrightText: 2024 Alec Delaney, for Adafruit Industries
2
+ SPDX-License-Identifier: MIT
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: circfirm
3
- Version: 3.0.0
3
+ Version: 4.0.0
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,35 +15,37 @@ 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
- Requires-Dist: boto3~=1.34
32
- Requires-Dist: click~=8.0
31
+ License-File: NOTICE
32
+ License-File: NOTICE.license
33
+ Requires-Dist: boto3~=1.35
34
+ Requires-Dist: click~=8.1
33
35
  Requires-Dist: click-spinner~=0.1
34
- Requires-Dist: packaging~=23.2
35
- Requires-Dist: psutil~=5.9
36
+ Requires-Dist: packaging~=24.2
37
+ Requires-Dist: psutil~=6.1
36
38
  Requires-Dist: pyyaml~=6.0
37
- Requires-Dist: requests~=2.31
38
- Requires-Dist: boto3-stubs[essential]~=1.34
39
+ Requires-Dist: requests~=2.32
40
+ Requires-Dist: boto3-stubs[essential]~=1.35
39
41
  Provides-Extra: dev
40
- Requires-Dist: build~=1.0; extra == "dev"
41
- Requires-Dist: coverage~=7.4; extra == "dev"
42
- Requires-Dist: pre-commit~=2.20; extra == "dev"
43
- Requires-Dist: pytest~=8.0; extra == "dev"
44
- 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"
45
47
  Requires-Dist: sphinx-tabs~=3.4; extra == "dev"
46
- Requires-Dist: sphinx-rtd-theme~=1.0; extra == "dev"
48
+ Requires-Dist: sphinx-rtd-theme~=3.0; extra == "dev"
47
49
 
48
50
  ..
49
51
  SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries
circfirm-4.0.0/VERSION ADDED
@@ -0,0 +1 @@
1
+ 4.0.0
@@ -8,6 +8,7 @@ Author(s): Alec Delaney
8
8
  """
9
9
 
10
10
  import enum
11
+ import re
11
12
 
12
13
 
13
14
  class Language(enum.Enum):
@@ -51,3 +52,20 @@ FIRMWARE_REGEX = (
51
52
  .replace(r"[language]", f"({_ALL_LANGUAGES_REGEX})")
52
53
  .replace(r"[version]", _VALID_VERSIONS_CAPTURE)
53
54
  )
55
+
56
+
57
+ def get_uf2_filename(board_id: str, version: str, language: str = "en_US") -> str:
58
+ """Get the structured name for a specific board/version CircuitPython."""
59
+ return f"adafruit-circuitpython-{board_id}-{language}-{version}.uf2"
60
+
61
+
62
+ def parse_firmware_info(uf2_filename: str) -> tuple[str, str]:
63
+ """Get firmware info."""
64
+ regex_match = re.match(FIRMWARE_REGEX, uf2_filename)
65
+ if regex_match is None:
66
+ raise ValueError(
67
+ "Firmware information could not be determined from the filename"
68
+ )
69
+ version = regex_match[3]
70
+ language = regex_match[2]
71
+ return version, language
@@ -9,8 +9,7 @@ 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
@@ -19,16 +18,11 @@ import circfirm.backend
19
18
  import circfirm.startup
20
19
 
21
20
 
22
- def get_uf2_filename(board_id: str, version: str, language: str = "en_US") -> str:
23
- """Get the structured name for a specific board/version CircuitPython."""
24
- return f"adafruit-circuitpython-{board_id}-{language}-{version}.uf2"
25
-
26
-
27
21
  def get_uf2_filepath(
28
22
  board_id: str, version: str, language: str = "en_US"
29
23
  ) -> pathlib.Path:
30
24
  """Get the path to a downloaded UF2 file."""
31
- file = get_uf2_filename(board_id, version, language)
25
+ file = circfirm.backend.get_uf2_filename(board_id, version, language)
32
26
  uf2_folder = get_board_folder(board_id)
33
27
  return uf2_folder / file
34
28
 
@@ -46,31 +40,33 @@ def is_downloaded(board_id: str, version: str, language: str = "en_US") -> bool:
46
40
 
47
41
  def download_uf2(board_id: str, version: str, language: str = "en_US") -> None:
48
42
  """Download a version of CircuitPython for a specific board."""
49
- file = get_uf2_filename(board_id, version, language=language)
43
+ file = circfirm.backend.get_uf2_filename(board_id, version, language=language)
50
44
  uf2_file = get_uf2_filepath(board_id, version, language=language)
51
45
  url = f"https://downloads.circuitpython.org/bin/{board_id}/{language}/{file}"
52
46
  response = requests.get(url)
53
47
 
54
48
  SUCCESS = 200
55
49
  if response.status_code != SUCCESS:
56
- raise ConnectionError(f"Could not download the specified UF2 file:\n{url}")
50
+ raise ConnectionError(
51
+ f"Could not download the specified UF2 file:\n{url}\nAre the board ID, version, and language correct?"
52
+ )
57
53
 
58
54
  uf2_file.parent.mkdir(parents=True, exist_ok=True)
59
55
  with open(uf2_file, mode="wb") as uf2file:
60
56
  uf2file.write(response.content)
61
57
 
62
58
 
63
- def get_sorted_boards(board_id: Optional[str]) -> Dict[str, Dict[str, Set[str]]]:
59
+ def get_sorted_boards(board_id: Optional[str]) -> dict[str, dict[str, set[str]]]:
64
60
  """Get a sorted collection of boards, versions, and languages."""
65
- boards: Dict[str, Dict[str, Set[str]]] = {}
61
+ boards: dict[str, dict[str, set[str]]] = {}
66
62
  for board_folder in sorted(os.listdir(circfirm.UF2_ARCHIVE)):
67
- versions: Dict[str, List[str]] = {}
68
- sorted_versions: Dict[str, Set[str]] = {}
63
+ versions: dict[str, list[str]] = {}
64
+ sorted_versions: dict[str, set[str]] = {}
69
65
  if board_id is not None and board_id != board_folder:
70
66
  continue
71
67
  board_folder_full = get_board_folder(board_folder)
72
68
  for item in os.listdir(board_folder_full):
73
- version, language = parse_firmware_info(item)
69
+ version, language = circfirm.backend.parse_firmware_info(item)
74
70
  try:
75
71
  version_set = set(versions[version])
76
72
  version_set.add(language)
@@ -83,15 +79,3 @@ def get_sorted_boards(board_id: Optional[str]) -> Dict[str, Dict[str, Set[str]]]
83
79
  sorted_versions[sorted_version] = versions[sorted_version]
84
80
  boards[board_folder] = sorted_versions
85
81
  return boards
86
-
87
-
88
- def parse_firmware_info(uf2_filename: str) -> Tuple[str, str]:
89
- """Get firmware info."""
90
- regex_match = re.match(circfirm.backend.FIRMWARE_REGEX, uf2_filename)
91
- if regex_match is None:
92
- raise ValueError(
93
- "Firmware information could not be determined from the filename"
94
- )
95
- version = regex_match[3]
96
- language = regex_match[2]
97
- return version, language
@@ -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,6 +17,7 @@ import packaging.version
17
17
  from mypy_boto3_s3 import S3ServiceResource
18
18
 
19
19
  import circfirm.backend
20
+ import circfirm.backend.cache
20
21
 
21
22
  S3_CONFIG = botocore.client.Config(signature_version=botocore.UNSIGNED)
22
23
  S3_RESOURCE: S3ServiceResource = boto3.resource("s3", config=S3_CONFIG)
@@ -26,24 +27,25 @@ BUCKET = S3_RESOURCE.Bucket(BUCKET_NAME)
26
27
 
27
28
  def get_board_versions(
28
29
  board_id: str, language: str = "en_US", *, regex: Optional[str] = None
29
- ) -> List[str]:
30
+ ) -> list[str]:
30
31
  """Get a list of CircuitPython versions for a given board."""
31
32
  prefix = f"bin/{board_id}/{language}"
32
33
  firmware_regex = circfirm.backend.FIRMWARE_REGEX_PATTERN.replace(
33
34
  r"[board]", board_id
34
35
  ).replace(r"[language]", language)
35
- version_regex = f"({regex})" if regex else circfirm.backend._VALID_VERSIONS_CAPTURE
36
+ version_regex = circfirm.backend._VALID_VERSIONS_CAPTURE
36
37
  firmware_regex = firmware_regex.replace(r"[version]", version_regex)
37
38
  s3_objects = BUCKET.objects.filter(Prefix=prefix)
38
39
  versions = set()
39
40
  for s3_object in s3_objects:
40
41
  result = re.match(f"{prefix}/{firmware_regex}", s3_object.key)
41
42
  if result:
42
- try:
43
- _ = packaging.version.Version(result[1])
44
- versions.add(result[1])
45
- except packaging.version.InvalidVersion:
46
- pass
43
+ if regex:
44
+ firmware_filename = s3_object.key.split("/")[-1]
45
+ version, _ = circfirm.backend.parse_firmware_info(firmware_filename)
46
+ if not re.match(regex, version):
47
+ continue
48
+ versions.add(result[1])
47
49
  return sorted(versions, key=packaging.version.Version, reverse=True)
48
50
 
49
51
 
@@ -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()
@@ -108,14 +122,15 @@ def copy_cache_firmware(
108
122
  announce_and_await(
109
123
  f"Copying UF2 to {board}", shutil.copyfile, args=(uf2file, uf2_path)
110
124
  )
111
- click.echo("Device should reboot momentarily.")
125
+ click.echo(f"CircuitPython version now upgraded to {version}")
126
+ click.echo("Device should reboot momentarily")
112
127
 
113
128
 
114
129
  def announce_and_await(
115
130
  msg: str,
116
131
  func: Callable[..., _T],
117
132
  args: Iterable = (),
118
- kwargs: Optional[Dict[str, Any]] = None,
133
+ kwargs: Optional[dict[str, Any]] = None,
119
134
  *,
120
135
  use_spinner: bool = True,
121
136
  ) -> _T:
@@ -140,7 +155,7 @@ def announce_and_await(
140
155
  raise err
141
156
 
142
157
 
143
- def get_settings() -> Dict[str, Any]:
158
+ def get_settings() -> dict[str, Any]:
144
159
  """Get the contents of the settings file."""
145
160
  with open(circfirm.SETTINGS_FILE, encoding="utf-8") as yamlfile:
146
161
  return yaml.safe_load(yamlfile)
@@ -9,14 +9,15 @@ Author(s): Alec Delaney
9
9
 
10
10
  import os
11
11
  import pathlib
12
+ import re
12
13
  import shutil
13
- import sys
14
14
  from typing import Optional
15
15
 
16
16
  import click
17
17
 
18
18
  import circfirm
19
19
  import circfirm.backend.cache
20
+ import circfirm.backend.s3
20
21
  import circfirm.cli
21
22
  import circfirm.startup
22
23
 
@@ -30,8 +31,18 @@ def cli():
30
31
  @click.option("-b", "--board-id", default=None, help="CircuitPython board ID")
31
32
  @click.option("-v", "--version", default=None, help="CircuitPython version")
32
33
  @click.option("-l", "--language", default=None, help="CircuitPython language/locale")
33
- def clear(
34
- board_id: Optional[str], version: Optional[str], language: Optional[str]
34
+ @click.option(
35
+ "-r",
36
+ "--regex",
37
+ is_flag=True,
38
+ default=False,
39
+ help="The board ID, version, and language options represent regex patterns",
40
+ )
41
+ def clear( # noqa: PLR0913
42
+ board_id: Optional[str],
43
+ version: Optional[str],
44
+ language: Optional[str],
45
+ regex: bool,
35
46
  ) -> None:
36
47
  """Clear the cache, either entirely or for a specific board/version."""
37
48
  if board_id is None and version is None and language is None:
@@ -40,13 +51,33 @@ def clear(
40
51
  click.echo("Cache cleared!")
41
52
  return
42
53
 
43
- glob_pattern = "*-*" if board_id is None else f"*-{board_id}"
44
- language_pattern = "-*" if language is None else f"-{language}"
45
- glob_pattern += language_pattern
46
- version_pattern = "-*" if version is None else f"-{version}.uf2"
47
- glob_pattern += version_pattern
54
+ if regex:
55
+ glob_pattern = "*-*-*-*"
56
+ else:
57
+ glob_pattern = "*-*" if board_id is None else f"*-{board_id}"
58
+ language_pattern = "-*" if language is None else f"-{language}"
59
+ glob_pattern += language_pattern
60
+ version_pattern = "-*" if version is None else f"-{version}.uf2"
61
+ glob_pattern += version_pattern
62
+
48
63
  matching_files = pathlib.Path(circfirm.UF2_ARCHIVE).rglob(glob_pattern)
64
+
49
65
  for matching_file in matching_files:
66
+ if regex:
67
+ board_id = ".*" if board_id is None else board_id
68
+ version = ".*" if version is None else version
69
+ language = ".*" if language is None else language
70
+
71
+ current_board_id = matching_file.parent.name
72
+ current_version, current_language = circfirm.backend.parse_firmware_info(
73
+ matching_file.name
74
+ )
75
+
76
+ board_id_matches = re.search(board_id, current_board_id)
77
+ version_matches = re.match(version, current_version)
78
+ language_matches = re.match(language, current_language)
79
+ if not all([board_id_matches, version_matches, language_matches]):
80
+ continue
50
81
  matching_file.unlink()
51
82
 
52
83
  # Delete board folder if empty
@@ -88,7 +119,7 @@ def cache_list(board_id: Optional[str]) -> None:
88
119
  @click.argument("version")
89
120
  @click.option("-l", "--language", default="en_US", help="CircuitPython language/locale")
90
121
  def cache_save(board_id: str, version: str, language: str) -> None:
91
- """Install a version of CircuitPython to cache."""
122
+ """Download a version of CircuitPython to the cache."""
92
123
  try:
93
124
  circfirm.cli.announce_and_await(
94
125
  f"Caching firmware version {version} for {board_id}",
@@ -97,3 +128,28 @@ def cache_save(board_id: str, version: str, language: str) -> None:
97
128
  )
98
129
  except ConnectionError as err:
99
130
  raise click.exceptions.ClickException(err.args[0])
131
+
132
+
133
+ @cli.command(name="latest")
134
+ @click.argument("board-id")
135
+ @click.option("-l", "--language", default="en_US", help="CircuitPython language/locale")
136
+ @click.option(
137
+ "-p",
138
+ "--pre-release",
139
+ is_flag=True,
140
+ default=False,
141
+ help="Whether pre-release versions should be considered",
142
+ )
143
+ def cache_latest(board_id: str, language: str, pre_release: bool) -> None:
144
+ """Download the latest version of CircuitPython to the cache."""
145
+ try:
146
+ version = circfirm.backend.s3.get_latest_board_version(
147
+ board_id, language, pre_release
148
+ )
149
+ circfirm.cli.announce_and_await(
150
+ f"Caching firmware version {version} for {board_id}",
151
+ circfirm.backend.cache.download_uf2,
152
+ args=(board_id, version, language),
153
+ )
154
+ except ConnectionError as err:
155
+ raise click.exceptions.ClickException(err.args[0])
@@ -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)