circup 2.0.4__tar.gz → 2.1.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 (82) hide show
  1. {circup-2.0.4 → circup-2.1.0}/CONTRIBUTING.rst +3 -3
  2. {circup-2.0.4/circup.egg-info → circup-2.1.0}/PKG-INFO +7 -6
  3. {circup-2.0.4 → circup-2.1.0}/README.rst +6 -5
  4. {circup-2.0.4 → circup-2.1.0}/circup/__init__.py +1 -1
  5. {circup-2.0.4 → circup-2.1.0}/circup/backends.py +81 -16
  6. {circup-2.0.4 → circup-2.1.0}/circup/command_utils.py +26 -0
  7. {circup-2.0.4 → circup-2.1.0}/circup/commands.py +2 -2
  8. {circup-2.0.4 → circup-2.1.0}/circup/shared.py +6 -2
  9. circup-2.1.0/circup/wwshell/README.rst +105 -0
  10. circup-2.1.0/circup/wwshell/README.rst.license +3 -0
  11. circup-2.1.0/circup/wwshell/__init__.py +14 -0
  12. circup-2.1.0/circup/wwshell/commands.py +231 -0
  13. {circup-2.0.4 → circup-2.1.0/circup.egg-info}/PKG-INFO +7 -6
  14. {circup-2.0.4 → circup-2.1.0}/circup.egg-info/SOURCES.txt +4 -0
  15. {circup-2.0.4 → circup-2.1.0}/circup.egg-info/entry_points.txt +1 -0
  16. {circup-2.0.4 → circup-2.1.0}/docs/conf.py +0 -1
  17. {circup-2.0.4 → circup-2.1.0}/docs/index.rst +3 -1
  18. {circup-2.0.4 → circup-2.1.0}/pyproject.toml +1 -0
  19. {circup-2.0.4 → circup-2.1.0}/.github/ISSUE_TEMPLATE.md +0 -0
  20. {circup-2.0.4 → circup-2.1.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  21. {circup-2.0.4 → circup-2.1.0}/.github/workflows/build.yml +0 -0
  22. {circup-2.0.4 → circup-2.1.0}/.github/workflows/release.yml +0 -0
  23. {circup-2.0.4 → circup-2.1.0}/.gitignore +0 -0
  24. {circup-2.0.4 → circup-2.1.0}/.isort.cfg +0 -0
  25. {circup-2.0.4 → circup-2.1.0}/.pre-commit-config.yaml +0 -0
  26. {circup-2.0.4 → circup-2.1.0}/.pylintrc +0 -0
  27. {circup-2.0.4 → circup-2.1.0}/CODE_OF_CONDUCT.rst +0 -0
  28. {circup-2.0.4 → circup-2.1.0}/CODE_OF_CONDUCT.rst.license +0 -0
  29. {circup-2.0.4 → circup-2.1.0}/CONTRIBUTING.rst.license +0 -0
  30. {circup-2.0.4 → circup-2.1.0}/LICENSE +0 -0
  31. {circup-2.0.4 → circup-2.1.0}/LICENSES/CC-BY-4.0.txt +0 -0
  32. {circup-2.0.4 → circup-2.1.0}/LICENSES/MIT.txt +0 -0
  33. {circup-2.0.4 → circup-2.1.0}/LICENSES/Unlicense.txt +0 -0
  34. {circup-2.0.4 → circup-2.1.0}/README.rst.license +0 -0
  35. {circup-2.0.4 → circup-2.1.0}/circup/bundle.py +0 -0
  36. {circup-2.0.4 → circup-2.1.0}/circup/config/bundle_config.json +0 -0
  37. {circup-2.0.4 → circup-2.1.0}/circup/config/bundle_config.json.license +0 -0
  38. {circup-2.0.4 → circup-2.1.0}/circup/logging.py +0 -0
  39. {circup-2.0.4 → circup-2.1.0}/circup/module.py +0 -0
  40. {circup-2.0.4 → circup-2.1.0}/circup.egg-info/dependency_links.txt +0 -0
  41. {circup-2.0.4 → circup-2.1.0}/circup.egg-info/requires.txt +0 -0
  42. {circup-2.0.4 → circup-2.1.0}/circup.egg-info/top_level.txt +0 -0
  43. {circup-2.0.4 → circup-2.1.0}/docs/_static/favicon.ico +0 -0
  44. {circup-2.0.4 → circup-2.1.0}/docs/_static/favicon.ico.license +0 -0
  45. {circup-2.0.4 → circup-2.1.0}/docs/index.rst.license +0 -0
  46. {circup-2.0.4 → circup-2.1.0}/docs/logo.png +0 -0
  47. {circup-2.0.4 → circup-2.1.0}/docs/logo.png.license +0 -0
  48. {circup-2.0.4 → circup-2.1.0}/optional_requirements.txt +0 -0
  49. {circup-2.0.4 → circup-2.1.0}/optional_requirements.txt.license +0 -0
  50. {circup-2.0.4 → circup-2.1.0}/readthedocs.yml +0 -0
  51. {circup-2.0.4 → circup-2.1.0}/requirements.txt +0 -0
  52. {circup-2.0.4 → circup-2.1.0}/requirements.txt.license +0 -0
  53. {circup-2.0.4 → circup-2.1.0}/setup.cfg +0 -0
  54. {circup-2.0.4 → circup-2.1.0}/tests/__init__.py +0 -0
  55. {circup-2.0.4 → circup-2.1.0}/tests/bad_module/__init__.py +0 -0
  56. {circup-2.0.4 → circup-2.1.0}/tests/bad_module/my_module.py +0 -0
  57. {circup-2.0.4 → circup-2.1.0}/tests/bad_python.py +0 -0
  58. {circup-2.0.4 → circup-2.1.0}/tests/bundle.json +0 -0
  59. {circup-2.0.4 → circup-2.1.0}/tests/bundle.json.license +0 -0
  60. {circup-2.0.4 → circup-2.1.0}/tests/device.json +0 -0
  61. {circup-2.0.4 → circup-2.1.0}/tests/device.json.license +0 -0
  62. {circup-2.0.4 → circup-2.1.0}/tests/dir_module/__init__.py +0 -0
  63. {circup-2.0.4 → circup-2.1.0}/tests/dir_module/my_module.py +0 -0
  64. {circup-2.0.4 → circup-2.1.0}/tests/import_styles.py +0 -0
  65. {circup-2.0.4 → circup-2.1.0}/tests/local_module.py +0 -0
  66. {circup-2.0.4 → circup-2.1.0}/tests/local_module_cp7.mpy +0 -0
  67. {circup-2.0.4 → circup-2.1.0}/tests/local_module_cp7.mpy.license +0 -0
  68. {circup-2.0.4 → circup-2.1.0}/tests/mock_device/boot_out.txt +0 -0
  69. {circup-2.0.4 → circup-2.1.0}/tests/mock_device/boot_out.txt.license +0 -0
  70. {circup-2.0.4 → circup-2.1.0}/tests/mock_device/lib/adafruit_waveform/.gitkeep +0 -0
  71. {circup-2.0.4 → circup-2.1.0}/tests/mount_exists.txt +0 -0
  72. {circup-2.0.4 → circup-2.1.0}/tests/mount_exists.txt.license +0 -0
  73. {circup-2.0.4 → circup-2.1.0}/tests/mount_missing.txt +0 -0
  74. {circup-2.0.4 → circup-2.1.0}/tests/mount_missing.txt.license +0 -0
  75. {circup-2.0.4 → circup-2.1.0}/tests/remote_module.py +0 -0
  76. {circup-2.0.4 → circup-2.1.0}/tests/test_bundle_config.json +0 -0
  77. {circup-2.0.4 → circup-2.1.0}/tests/test_bundle_config.json.license +0 -0
  78. {circup-2.0.4 → circup-2.1.0}/tests/test_bundle_config_local.json +0 -0
  79. {circup-2.0.4 → circup-2.1.0}/tests/test_bundle_config_local.json.license +0 -0
  80. {circup-2.0.4 → circup-2.1.0}/tests/test_circup.py +0 -0
  81. {circup-2.0.4 → circup-2.1.0}/tests/test_module.mpy +0 -0
  82. {circup-2.0.4 → circup-2.1.0}/tests/test_module.mpy.license +0 -0
@@ -25,7 +25,7 @@ Developer Setup
25
25
 
26
26
  .. note::
27
27
 
28
- Please try to use Python 3.9+ while developing CircUp. This is so we can
28
+ Please try to use Python 3.9+ while developing Circup. This is so we can
29
29
  use the
30
30
  `Black code formatter <https://black.readthedocs.io/en/stable/index.html>`_
31
31
  and so that we're supporting versions which still receive security updates.
@@ -100,7 +100,7 @@ subsequently used to facilitate the various commands the tool makes available.
100
100
 
101
101
  These commands are defined at the very end of the ``circup.py`` code.
102
102
 
103
- Unit tests can be found in the ``tests`` directory. CircUp uses
103
+ Unit tests can be found in the ``tests`` directory. Circup uses
104
104
  `pytest <http://www.pytest.org/en/latest/>`_ style testing conventions. Test
105
105
  functions should include a comment to describe its *intention*. We currently
106
106
  have 100% unit test coverage for all the core functionality (excluding
@@ -124,7 +124,7 @@ available options to help you work with the code base.
124
124
  Before submitting a PR, please remember to ``pre-commit run --all-files``.
125
125
  But if you forget the CI process in Github will run it for you. ;-)
126
126
 
127
- CircUp uses the `Click <https://click.palletsprojects.com/en/7.x/>`_ module to
127
+ Circup uses the `Click <https://click.palletsprojects.com>`_ module to
128
128
  run command-line interaction. The
129
129
  `AppDirs <https://pypi.org/project/appdirs/>`_ module is used to determine
130
130
  where to store user-specific assets created by the tool in such a way that
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: circup
3
- Version: 2.0.4
3
+ Version: 2.1.0
4
4
  Summary: A tool to manage/update libraries on CircuitPython devices.
5
5
  Author-email: Adafruit Industries <circuitpython@adafruit.com>
6
6
  License: MIT License
@@ -59,7 +59,7 @@ Requires-Dist: pytest-faulthandler; extra == "optional"
59
59
  Requires-Dist: pytest-random-order; extra == "optional"
60
60
 
61
61
 
62
- CircUp
62
+ Circup
63
63
  ======
64
64
 
65
65
  .. image:: https://readthedocs.org/projects/circup/badge/?version=latest
@@ -88,7 +88,7 @@ A tool to manage and update libraries (modules) on a CircuitPython device.
88
88
  Installation
89
89
  ------------
90
90
 
91
- Circup requires Python 3.5 or higher.
91
+ Circup requires Python 3.9 or higher.
92
92
 
93
93
  In a `virtualenv <https://virtualenv.pypa.io/en/latest/>`_,
94
94
  ``pip install circup`` should do the trick. This is the simplest way to make it
@@ -99,7 +99,7 @@ If you have no idea what a virtualenv is, try the following command,
99
99
 
100
100
  .. note::
101
101
 
102
- If you use the ``pip3`` command to install CircUp you must make sure that
102
+ If you use the ``pip3`` command to install Circup you must make sure that
103
103
  your path contains the directory into which the script will be installed.
104
104
  To discover this path,
105
105
 
@@ -136,7 +136,7 @@ Usage
136
136
  -----
137
137
 
138
138
  If you need more detailed help using Circup see the Learn Guide article
139
- `"Use CircUp to easily keep your CircuitPython libraries up to date" <https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup/>`_.
139
+ `"Use Circup to easily keep your CircuitPython libraries up to date" <https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup/>`_.
140
140
 
141
141
  First, plug in a device running CircuiPython. This should appear as a mounted
142
142
  storage device called ``CIRCUITPY``.
@@ -290,7 +290,7 @@ The ``--version`` flag will tell you the current version of the
290
290
  ``circup`` command itself::
291
291
 
292
292
  $ circup --version
293
- CircUp, A CircuitPython module updater. Version 0.0.1
293
+ Circup, A CircuitPython module updater. Version 0.0.1
294
294
 
295
295
 
296
296
  To use circup via the `Web Workflow <https://learn.adafruit.com/getting-started-with-web-workflow-using-the-code-editor>`_. on devices that support it. Use the ``--host`` and ``--password`` arguments before your circup command.::
@@ -323,6 +323,7 @@ For Bash, add this to ~/.bashrc::
323
323
 
324
324
  For Zsh, add this to ~/.zshrc::
325
325
 
326
+ autoload -U compinit; compinit
326
327
  eval "$(_CIRCUP_COMPLETE=zsh_source circup)"
327
328
 
328
329
  For Fish, add this to ~/.config/fish/completions/foo-bar.fish::
@@ -1,5 +1,5 @@
1
1
 
2
- CircUp
2
+ Circup
3
3
  ======
4
4
 
5
5
  .. image:: https://readthedocs.org/projects/circup/badge/?version=latest
@@ -28,7 +28,7 @@ A tool to manage and update libraries (modules) on a CircuitPython device.
28
28
  Installation
29
29
  ------------
30
30
 
31
- Circup requires Python 3.5 or higher.
31
+ Circup requires Python 3.9 or higher.
32
32
 
33
33
  In a `virtualenv <https://virtualenv.pypa.io/en/latest/>`_,
34
34
  ``pip install circup`` should do the trick. This is the simplest way to make it
@@ -39,7 +39,7 @@ If you have no idea what a virtualenv is, try the following command,
39
39
 
40
40
  .. note::
41
41
 
42
- If you use the ``pip3`` command to install CircUp you must make sure that
42
+ If you use the ``pip3`` command to install Circup you must make sure that
43
43
  your path contains the directory into which the script will be installed.
44
44
  To discover this path,
45
45
 
@@ -76,7 +76,7 @@ Usage
76
76
  -----
77
77
 
78
78
  If you need more detailed help using Circup see the Learn Guide article
79
- `"Use CircUp to easily keep your CircuitPython libraries up to date" <https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup/>`_.
79
+ `"Use Circup to easily keep your CircuitPython libraries up to date" <https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup/>`_.
80
80
 
81
81
  First, plug in a device running CircuiPython. This should appear as a mounted
82
82
  storage device called ``CIRCUITPY``.
@@ -230,7 +230,7 @@ The ``--version`` flag will tell you the current version of the
230
230
  ``circup`` command itself::
231
231
 
232
232
  $ circup --version
233
- CircUp, A CircuitPython module updater. Version 0.0.1
233
+ Circup, A CircuitPython module updater. Version 0.0.1
234
234
 
235
235
 
236
236
  To use circup via the `Web Workflow <https://learn.adafruit.com/getting-started-with-web-workflow-using-the-code-editor>`_. on devices that support it. Use the ``--host`` and ``--password`` arguments before your circup command.::
@@ -263,6 +263,7 @@ For Bash, add this to ~/.bashrc::
263
263
 
264
264
  For Zsh, add this to ~/.zshrc::
265
265
 
266
+ autoload -U compinit; compinit
266
267
  eval "$(_CIRCUP_COMPLETE=zsh_source circup)"
267
268
 
268
269
  For Fish, add this to ~/.config/fish/completions/foo-bar.fish::
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
4
  """
5
- CircUp -- a utility to manage and update libraries on a CircuitPython device.
5
+ Circup -- a utility to manage and update libraries on a CircuitPython device.
6
6
  """
7
7
 
8
8
 
@@ -73,7 +73,7 @@ class Backend:
73
73
  """
74
74
  return self.get_modules(os.path.join(self.device_location, self.LIB_DIR_PATH))
75
75
 
76
- def _create_library_directory(self, device_path, library_path):
76
+ def create_directory(self, device_path, directory):
77
77
  """
78
78
  To be overridden by subclass
79
79
  """
@@ -97,6 +97,12 @@ class Backend:
97
97
  """
98
98
  raise NotImplementedError
99
99
 
100
+ def upload_file(self, target_file, location_to_paste):
101
+ """Paste a copy of the specified file at the location given
102
+ To be overridden by subclass
103
+ """
104
+ raise NotImplementedError
105
+
100
106
  # pylint: disable=too-many-locals,too-many-branches,too-many-arguments,too-many-nested-blocks,too-many-statements
101
107
  def install_module(
102
108
  self, device_path, device_modules, name, pyext, mod_names, upgrade=False
@@ -189,7 +195,7 @@ class Backend:
189
195
  return
190
196
 
191
197
  # Create the library directory first.
192
- self._create_library_directory(device_path, library_path)
198
+ self.create_directory(device_path, library_path)
193
199
  if local_path is None:
194
200
  if pyext:
195
201
  # Use Python source for module.
@@ -281,7 +287,9 @@ class WebBackend(Backend):
281
287
  ):
282
288
  super().__init__(logger)
283
289
  if password is None:
284
- raise ValueError("--host needs --password")
290
+ raise ValueError(
291
+ "Must pass --password or set CIRCUP_WEBWORKFLOW_PASSWORD environment variable"
292
+ )
285
293
 
286
294
  # pylint: disable=no-member
287
295
  # verify hostname/address
@@ -306,6 +314,7 @@ class WebBackend(Backend):
306
314
  self.library_path = self.device_location + "/" + self.LIB_DIR_PATH
307
315
  self.timeout = timeout
308
316
  self.version_override = version_override
317
+ self.FS_URL = urljoin(self.device_location, self.FS_PATH)
309
318
 
310
319
  def __repr__(self):
311
320
  return f"<WebBackend @{self.device_location}>"
@@ -546,10 +555,9 @@ class WebBackend(Backend):
546
555
  metadata["path"] = sfm_url
547
556
  result[sfm[:idx]] = metadata
548
557
 
549
- def _create_library_directory(self, device_path, library_path):
550
- url = urlparse(device_path)
551
- auth = HTTPBasicAuth("", url.password)
552
- with self.session.put(library_path, auth=auth, timeout=self.timeout) as r:
558
+ def create_directory(self, device_path, directory):
559
+ auth = HTTPBasicAuth("", self.password)
560
+ with self.session.put(directory, auth=auth, timeout=self.timeout) as r:
553
561
  if r.status_code == 409:
554
562
  _writeable_error()
555
563
  r.raise_for_status()
@@ -560,11 +568,56 @@ class WebBackend(Backend):
560
568
  self.device_location,
561
569
  "/".join(("fs", location_to_paste, target_file, "")),
562
570
  )
563
- self._create_library_directory(self.device_location, create_directory_url)
571
+ self.create_directory(self.device_location, create_directory_url)
564
572
  self.install_dir_http(target_file)
565
573
  else:
566
574
  self.install_file_http(target_file)
567
575
 
576
+ def upload_file(self, target_file, location_to_paste):
577
+ """
578
+ copy a file from the host PC to the microcontroller
579
+ :param target_file: file on the host PC to copy
580
+ :param location_to_paste: Location on the microcontroller to paste it.
581
+ :return:
582
+ """
583
+ if os.path.isdir(target_file):
584
+ create_directory_url = urljoin(
585
+ self.device_location,
586
+ "/".join(("fs", location_to_paste, target_file, "")),
587
+ )
588
+ self.create_directory(self.device_location, create_directory_url)
589
+ self.install_dir_http(target_file, location_to_paste)
590
+ else:
591
+ self.install_file_http(target_file, location_to_paste)
592
+
593
+ def download_file(self, target_file, location_to_paste):
594
+ """
595
+ Download a file from the MCU device to the local host PC
596
+ :param target_file: The file on the MCU to download
597
+ :param location_to_paste: The location on the host PC to put the downloaded copy.
598
+ :return:
599
+ """
600
+ auth = HTTPBasicAuth("", self.password)
601
+ with self.session.get(
602
+ self.FS_URL + target_file, timeout=self.timeout, auth=auth
603
+ ) as r:
604
+ if r.status_code == 404:
605
+ click.secho(f"{target_file} was not found on the device", "red")
606
+
607
+ file_name = target_file.split("/")[-1]
608
+ if location_to_paste is None:
609
+ with open(file_name, "wb") as f:
610
+ f.write(r.content)
611
+
612
+ click.echo(f"Downloaded File: {file_name}")
613
+ else:
614
+ with open(os.path.join(location_to_paste, file_name), "wb") as f:
615
+ f.write(r.content)
616
+
617
+ click.echo(
618
+ f"Downloaded File: {os.path.join(location_to_paste, file_name)}"
619
+ )
620
+
568
621
  def install_module_mpy(self, bundle, metadata):
569
622
  """
570
623
  :param bundle library bundle.
@@ -668,11 +721,7 @@ class WebBackend(Backend):
668
721
  """
669
722
  retuns the full path on the device to a given file name.
670
723
  """
671
- return urljoin(
672
- urljoin(self.device_location, "fs/", allow_fragments=False),
673
- filename,
674
- allow_fragments=False,
675
- )
724
+ return "/".join((self.device_location, "fs", filename))
676
725
 
677
726
  def is_device_present(self):
678
727
  """
@@ -743,6 +792,19 @@ class WebBackend(Backend):
743
792
  return r.json()["free"] * r.json()["block_size"] # bytes
744
793
  sys.exit(1)
745
794
 
795
+ def list_dir(self, dirpath):
796
+ """
797
+ Returns the list of files located in the given dirpath.
798
+ """
799
+ auth = HTTPBasicAuth("", self.password)
800
+ with self.session.get(
801
+ urljoin(self.device_location, f"fs/{dirpath if dirpath else ''}"),
802
+ auth=auth,
803
+ headers={"Accept": "application/json"},
804
+ timeout=self.timeout,
805
+ ) as r:
806
+ return r.json()["files"]
807
+
746
808
 
747
809
  class DiskBackend(Backend):
748
810
  """
@@ -821,9 +883,9 @@ class DiskBackend(Backend):
821
883
  """
822
884
  return _get_modules_file(device_lib_path, self.logger)
823
885
 
824
- def _create_library_directory(self, device_path, library_path):
825
- if not os.path.exists(library_path): # pragma: no cover
826
- os.makedirs(library_path)
886
+ def create_directory(self, device_path, directory):
887
+ if not os.path.exists(directory): # pragma: no cover
888
+ os.makedirs(directory)
827
889
 
828
890
  def copy_file(self, target_file, location_to_paste):
829
891
  target_filename = target_file.split(os.path.sep)[-1]
@@ -838,6 +900,9 @@ class DiskBackend(Backend):
838
900
  os.path.join(self.device_location, location_to_paste, target_filename),
839
901
  )
840
902
 
903
+ def upload_file(self, target_file, location_to_paste):
904
+ self.copy_file(target_file, location_to_paste)
905
+
841
906
  def install_module_mpy(self, bundle, metadata):
842
907
  """
843
908
  :param bundle library bundle.
@@ -625,3 +625,29 @@ def get_device_path(host, port, password, path):
625
625
  else:
626
626
  device_path = find_device()
627
627
  return device_path
628
+
629
+
630
+ def sorted_by_directory_then_alpha(list_of_files):
631
+ """
632
+ Sort the list of files into alphabetical seperated
633
+ with directories grouped together before files.
634
+ """
635
+ dirs = {}
636
+ files = {}
637
+
638
+ for cur_file in list_of_files:
639
+ if cur_file["directory"]:
640
+ dirs[cur_file["name"]] = cur_file
641
+ else:
642
+ files[cur_file["name"]] = cur_file
643
+
644
+ sorted_dir_names = sorted(dirs.keys())
645
+ sorted_file_names = sorted(files.keys())
646
+
647
+ sorted_full_list = []
648
+ for cur_name in sorted_dir_names:
649
+ sorted_full_list.append(dirs[cur_name])
650
+ for cur_name in sorted_file_names:
651
+ sorted_full_list.append(files[cur_name])
652
+
653
+ return sorted_full_list
@@ -84,7 +84,7 @@ from circup.command_utils import (
84
84
  "with --board-id, it overrides the detected CPy version.",
85
85
  )
86
86
  @click.version_option(
87
- prog_name="CircUp",
87
+ prog_name="Circup",
88
88
  message="%(prog)s, A CircuitPython module updater. Version %(version)s",
89
89
  )
90
90
  @click.pass_context
@@ -94,7 +94,7 @@ def main( # pylint: disable=too-many-locals
94
94
  """
95
95
  A tool to manage and update libraries on a CircuitPython device.
96
96
  """
97
- # pylint: disable=too-many-arguments,too-many-branches,too-many-statements,too-many-locals
97
+ # pylint: disable=too-many-arguments,too-many-branches,too-many-statements,too-many-locals, R0801
98
98
  ctx.ensure_object(dict)
99
99
  ctx.obj["TIMEOUT"] = timeout
100
100
 
@@ -79,14 +79,18 @@ def _get_modules_file(path, logger):
79
79
  py_files = glob.glob(os.path.join(package_path, "**/*.py"), recursive=True)
80
80
  mpy_files = glob.glob(os.path.join(package_path, "**/*.mpy"), recursive=True)
81
81
  all_files = py_files + mpy_files
82
+ # put __init__ first if any, assumed to have the version number
83
+ all_files.sort()
82
84
  # default value
83
85
  result[name] = {"path": package_path, "mpy": bool(mpy_files)}
84
86
  # explore all the submodules to detect bad ones
85
87
  for source in [f for f in all_files if not os.path.basename(f).startswith(".")]:
86
88
  metadata = extract_metadata(source, logger)
87
89
  if "__version__" in metadata:
88
- metadata["path"] = package_path
89
- result[name] = metadata
90
+ # don't replace metadata if already found
91
+ if "__version__" not in result[name]:
92
+ metadata["path"] = package_path
93
+ result[name] = metadata
90
94
  # break now if any of the submodules has a bad format
91
95
  if metadata["__version__"] == BAD_FILE_FORMAT:
92
96
  break
@@ -0,0 +1,105 @@
1
+
2
+ wwshell
3
+ =======
4
+
5
+ .. image:: https://readthedocs.org/projects/circup/badge/?version=latest
6
+ :target: https://circuitpython.readthedocs.io/projects/circup/en/latest/
7
+ :alt: Documentation Status
8
+
9
+ .. image:: https://img.shields.io/discord/327254708534116352.svg
10
+ :target: https://adafru.it/discord
11
+ :alt: Discord
12
+
13
+
14
+ .. image:: https://github.com/adafruit/circup/workflows/Build%20CI/badge.svg
15
+ :target: https://github.com/adafruit/circup/actions
16
+ :alt: Build Status
17
+
18
+
19
+ .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
20
+ :target: https://github.com/psf/black
21
+ :alt: Code Style: Black
22
+
23
+
24
+ A tool to manage files on a CircuitPython device via wireless workflows.
25
+ Currently supports Web Workflow.
26
+
27
+ .. contents::
28
+
29
+ Installation
30
+ ------------
31
+
32
+ wwshell is bundled along with Circup. When you install Circup you'll get wwshell automatically.
33
+
34
+ Circup requires Python 3.5 or higher.
35
+
36
+ In a `virtualenv <https://virtualenv.pypa.io/en/latest/>`_,
37
+ ``pip install circup`` should do the trick. This is the simplest way to make it
38
+ work.
39
+
40
+ If you have no idea what a virtualenv is, try the following command,
41
+ ``pip3 install --user circup``.
42
+
43
+ .. note::
44
+
45
+ If you use the ``pip3`` command to install CircUp you must make sure that
46
+ your path contains the directory into which the script will be installed.
47
+ To discover this path,
48
+
49
+ * On Unix-like systems, type ``python3 -m site --user-base`` and append
50
+ ``bin`` to the resulting path.
51
+ * On Windows, type the same command, but append ``Scripts`` to the
52
+ resulting path.
53
+
54
+ What does wwshell do?
55
+ ---------------------
56
+
57
+ It lets you view, delete, upload, and download files from your Circuitpython device
58
+ via wireless workflows. Similar to ampy, but operates over wireless workflow rather
59
+ than USB serial.
60
+
61
+ Usage
62
+ -----
63
+
64
+ To use web workflow you need to enable it by putting WIFI credentials and a web workflow
65
+ password into your settings.toml file. `See here <https://learn.adafruit.com/getting-started-with-web-workflow-using-the-code-editor/device-setup>`_,
66
+
67
+ To get help, just type the command::
68
+
69
+ $ wwshell
70
+ Usage: wwshell [OPTIONS] COMMAND [ARGS]...
71
+
72
+ A tool to manage files CircuitPython device over web workflow.
73
+
74
+ Options:
75
+ --verbose Comprehensive logging is sent to stdout.
76
+ --path DIRECTORY Path to CircuitPython directory. Overrides automatic path
77
+ detection.
78
+ --host TEXT Hostname or IP address of a device. Overrides automatic
79
+ path detection.
80
+ --password TEXT Password to use for authentication when --host is used.
81
+ You can optionally set an environment variable
82
+ CIRCUP_WEBWORKFLOW_PASSWORD instead of passing this
83
+ argument. If both exist the CLI arg takes precedent.
84
+ --timeout INTEGER Specify the timeout in seconds for any network
85
+ operations.
86
+ --version Show the version and exit.
87
+ --help Show this message and exit.
88
+
89
+ Commands:
90
+ get Download a copy of a file or directory from the device to the...
91
+ ls Lists the contents of a directory.
92
+ put Upload a copy of a file or directory from the local computer to...
93
+ rm Delete a file on the device.
94
+
95
+
96
+ .. note::
97
+
98
+ If you find a bug, or you want to suggest an enhancement or new feature
99
+ feel free to create an issue or submit a pull request here:
100
+
101
+ https://github.com/adafruit/circup
102
+
103
+
104
+ Discussion of this tool happens on the Adafruit CircuitPython
105
+ `Discord channel <https://discord.gg/rqrKDjU>`_.
@@ -0,0 +1,3 @@
1
+ # SPDX-FileCopyrightText: 2024 Tim Cocks, written for Adafruit Industries
2
+ #
3
+ # SPDX-License-Identifier: MIT
@@ -0,0 +1,14 @@
1
+ # SPDX-FileCopyrightText: 2024 Tim Cocks, written for Adafruit Industries
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ """
5
+ wwshell is a CLI utility for managing files on CircuitPython devices via wireless workflows.
6
+ It currently supports Web Workflow.
7
+ """
8
+ from .commands import main
9
+
10
+
11
+ # Allows execution via `python -m circup ...`
12
+ # pylint: disable=no-value-for-parameter
13
+ if __name__ == "__main__":
14
+ main()
@@ -0,0 +1,231 @@
1
+ # SPDX-FileCopyrightText: 2019 Nicholas Tollervey, 2024 Tim Cocks, written for Adafruit Industries
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ """
5
+ # ----------- CLI command definitions ----------- #
6
+
7
+ The following functions have IO side effects (for instance they emit to
8
+ stdout). Ergo, these are not checked with unit tests. Most of the
9
+ functionality they provide is provided by the functions from util_functions.py,
10
+ and the respective Backends which *are* tested. Most of the logic of the following
11
+ functions is to prepare things for presentation to / interaction with the user.
12
+ """
13
+ import os
14
+ import time
15
+ import sys
16
+ import logging
17
+ import update_checker
18
+ import click
19
+ import requests
20
+
21
+
22
+ from circup.backends import WebBackend
23
+ from circup.logging import logger, log_formatter, LOGFILE
24
+ from circup.shared import BOARDLESS_COMMANDS
25
+
26
+ from circup.command_utils import (
27
+ get_device_path,
28
+ get_circup_version,
29
+ sorted_by_directory_then_alpha,
30
+ )
31
+
32
+
33
+ @click.group()
34
+ @click.option(
35
+ "--verbose", is_flag=True, help="Comprehensive logging is sent to stdout."
36
+ )
37
+ @click.option(
38
+ "--path",
39
+ type=click.Path(exists=True, file_okay=False),
40
+ help="Path to CircuitPython directory. Overrides automatic path detection.",
41
+ )
42
+ @click.option(
43
+ "--host",
44
+ help="Hostname or IP address of a device. Overrides automatic path detection.",
45
+ default="circuitpython.local",
46
+ )
47
+ @click.option(
48
+ "--port",
49
+ help="HTTP port that the web workflow is listening on.",
50
+ default=80,
51
+ )
52
+ @click.option(
53
+ "--password",
54
+ help="Password to use for authentication when --host is used."
55
+ " You can optionally set an environment variable CIRCUP_WEBWORKFLOW_PASSWORD"
56
+ " instead of passing this argument. If both exist the CLI arg takes precedent.",
57
+ )
58
+ @click.option(
59
+ "--timeout",
60
+ default=30,
61
+ help="Specify the timeout in seconds for any network operations.",
62
+ )
63
+ @click.version_option(
64
+ prog_name="CircFile",
65
+ message="%(prog)s, A CircuitPython web workflow file managemenr. Version %(version)s",
66
+ )
67
+ @click.pass_context
68
+ def main( # pylint: disable=too-many-locals
69
+ ctx,
70
+ verbose,
71
+ path,
72
+ host,
73
+ port,
74
+ password,
75
+ timeout,
76
+ ): # pragma: no cover
77
+ """
78
+ A tool to manage files CircuitPython device over web workflow.
79
+ """
80
+ # pylint: disable=too-many-arguments,too-many-branches,too-many-statements,too-many-locals, R0801
81
+ ctx.ensure_object(dict)
82
+ ctx.obj["TIMEOUT"] = timeout
83
+
84
+ if password is None:
85
+ password = os.getenv("CIRCUP_WEBWORKFLOW_PASSWORD")
86
+
87
+ device_path = get_device_path(host, port, password, path)
88
+
89
+ using_webworkflow = "host" in ctx.params.keys() and ctx.params["host"] is not None
90
+ if using_webworkflow:
91
+ if host == "circuitpython.local":
92
+ click.echo("Checking versions.json on circuitpython.local to find hostname")
93
+ versions_resp = requests.get(
94
+ "http://circuitpython.local/cp/version.json", timeout=timeout
95
+ )
96
+ host = f'{versions_resp.json()["hostname"]}.local'
97
+ click.echo(f"Using hostname: {host}")
98
+ device_path = device_path.replace("circuitpython.local", host)
99
+ try:
100
+ ctx.obj["backend"] = WebBackend(
101
+ host=host, port=port, password=password, logger=logger, timeout=timeout
102
+ )
103
+ except ValueError as e:
104
+ click.secho(e, fg="red")
105
+ time.sleep(0.3)
106
+ sys.exit(1)
107
+ except RuntimeError as e:
108
+ click.secho(e, fg="red")
109
+ sys.exit(1)
110
+
111
+ if verbose:
112
+ # Configure additional logging to stdout.
113
+ ctx.obj["verbose"] = True
114
+ verbose_handler = logging.StreamHandler(sys.stdout)
115
+ verbose_handler.setLevel(logging.INFO)
116
+ verbose_handler.setFormatter(log_formatter)
117
+ logger.addHandler(verbose_handler)
118
+ click.echo("Logging to {}\n".format(LOGFILE))
119
+ else:
120
+ ctx.obj["verbose"] = False
121
+
122
+ logger.info("### Started Circfile ###")
123
+
124
+ # If a newer version of circfile is available, print a message.
125
+ logger.info("Checking for a newer version of circfile")
126
+ version = get_circup_version()
127
+ if version:
128
+ update_checker.update_check("circfile", version)
129
+
130
+ # stop early if the command is boardless
131
+ if ctx.invoked_subcommand in BOARDLESS_COMMANDS or "--help" in sys.argv:
132
+ return
133
+
134
+ ctx.obj["DEVICE_PATH"] = device_path
135
+
136
+ if device_path is None or not ctx.obj["backend"].is_device_present():
137
+ click.secho("Could not find a connected CircuitPython device.", fg="red")
138
+ sys.exit(1)
139
+ else:
140
+ click.echo("Found device at {}.".format(device_path))
141
+
142
+
143
+ @main.command("ls")
144
+ @click.argument("file", required=True, nargs=1, default="/")
145
+ @click.pass_context
146
+ def ls_cli(ctx, file): # pragma: no cover
147
+ """
148
+ Lists the contents of a directory. Defaults to root directory
149
+ if not supplied.
150
+ """
151
+ logger.info("ls")
152
+ if not file.endswith("/"):
153
+ file += "/"
154
+ click.echo(f"running: ls {file}")
155
+
156
+ files = ctx.obj["backend"].list_dir(file)
157
+ click.echo("Size\tName")
158
+ for cur_file in sorted_by_directory_then_alpha(files):
159
+ click.echo(
160
+ f"{cur_file['file_size']}\t{cur_file['name']}{'/' if cur_file['directory'] else ''}"
161
+ )
162
+
163
+
164
+ @main.command("put")
165
+ @click.argument("file", required=True, nargs=1)
166
+ @click.argument("location", required=False, nargs=1, default="")
167
+ @click.option("--overwrite", is_flag=True, help="Overwrite the file if it exists.")
168
+ @click.pass_context
169
+ def put_cli(ctx, file, location, overwrite):
170
+ """
171
+ Upload a copy of a file or directory from the local computer
172
+ to the device
173
+ """
174
+ click.echo(f"Attempting PUT: {file} at {location} overwrite? {overwrite}")
175
+ if not ctx.obj["backend"].file_exists(f"{location}{file}"):
176
+ ctx.obj["backend"].upload_file(file, location)
177
+ click.echo(f"Successfully PUT {location}{file}")
178
+ else:
179
+ if overwrite:
180
+ click.secho(
181
+ f"{location}{file} already exists. Overwriting it.", fg="yellow"
182
+ )
183
+ ctx.obj["backend"].upload_file(file, location)
184
+ click.echo(f"Successfully PUT {location}{file}")
185
+ else:
186
+ click.secho(
187
+ f"{location}{file} already exists. Pass --overwrite if you wish to replace it.",
188
+ fg="red",
189
+ )
190
+
191
+
192
+ # pylint: enable=too-many-arguments,too-many-locals
193
+
194
+
195
+ @main.command("get")
196
+ @click.argument("file", required=True, nargs=1)
197
+ @click.argument("location", required=False, nargs=1)
198
+ @click.pass_context
199
+ def get_cli(ctx, file, location): # pragma: no cover
200
+ """
201
+ Download a copy of a file or directory from the device to the local computer.
202
+ """
203
+
204
+ click.echo(f"running: get {file} {location}")
205
+ ctx.obj["backend"].download_file(file, location)
206
+
207
+
208
+ @main.command("rm")
209
+ @click.argument("file", nargs=1)
210
+ @click.pass_context
211
+ def rm_cli(ctx, file): # pragma: no cover
212
+ """
213
+ Delete a file on the device.
214
+ """
215
+ click.echo(f"running: rm {file}")
216
+ ctx.obj["backend"].uninstall(
217
+ ctx.obj["backend"].device_location, ctx.obj["backend"].get_file_path(file)
218
+ )
219
+
220
+
221
+ @main.command("mkdir")
222
+ @click.argument("directory", nargs=1)
223
+ @click.pass_context
224
+ def mkdir_cli(ctx, directory): # pragma: no cover
225
+ """
226
+ Create
227
+ """
228
+ click.echo(f"running: mkdir {directory}")
229
+ ctx.obj["backend"].create_directory(
230
+ ctx.obj["backend"].device_location, ctx.obj["backend"].get_file_path(directory)
231
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: circup
3
- Version: 2.0.4
3
+ Version: 2.1.0
4
4
  Summary: A tool to manage/update libraries on CircuitPython devices.
5
5
  Author-email: Adafruit Industries <circuitpython@adafruit.com>
6
6
  License: MIT License
@@ -59,7 +59,7 @@ Requires-Dist: pytest-faulthandler; extra == "optional"
59
59
  Requires-Dist: pytest-random-order; extra == "optional"
60
60
 
61
61
 
62
- CircUp
62
+ Circup
63
63
  ======
64
64
 
65
65
  .. image:: https://readthedocs.org/projects/circup/badge/?version=latest
@@ -88,7 +88,7 @@ A tool to manage and update libraries (modules) on a CircuitPython device.
88
88
  Installation
89
89
  ------------
90
90
 
91
- Circup requires Python 3.5 or higher.
91
+ Circup requires Python 3.9 or higher.
92
92
 
93
93
  In a `virtualenv <https://virtualenv.pypa.io/en/latest/>`_,
94
94
  ``pip install circup`` should do the trick. This is the simplest way to make it
@@ -99,7 +99,7 @@ If you have no idea what a virtualenv is, try the following command,
99
99
 
100
100
  .. note::
101
101
 
102
- If you use the ``pip3`` command to install CircUp you must make sure that
102
+ If you use the ``pip3`` command to install Circup you must make sure that
103
103
  your path contains the directory into which the script will be installed.
104
104
  To discover this path,
105
105
 
@@ -136,7 +136,7 @@ Usage
136
136
  -----
137
137
 
138
138
  If you need more detailed help using Circup see the Learn Guide article
139
- `"Use CircUp to easily keep your CircuitPython libraries up to date" <https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup/>`_.
139
+ `"Use Circup to easily keep your CircuitPython libraries up to date" <https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup/>`_.
140
140
 
141
141
  First, plug in a device running CircuiPython. This should appear as a mounted
142
142
  storage device called ``CIRCUITPY``.
@@ -290,7 +290,7 @@ The ``--version`` flag will tell you the current version of the
290
290
  ``circup`` command itself::
291
291
 
292
292
  $ circup --version
293
- CircUp, A CircuitPython module updater. Version 0.0.1
293
+ Circup, A CircuitPython module updater. Version 0.0.1
294
294
 
295
295
 
296
296
  To use circup via the `Web Workflow <https://learn.adafruit.com/getting-started-with-web-workflow-using-the-code-editor>`_. on devices that support it. Use the ``--host`` and ``--password`` arguments before your circup command.::
@@ -323,6 +323,7 @@ For Bash, add this to ~/.bashrc::
323
323
 
324
324
  For Zsh, add this to ~/.zshrc::
325
325
 
326
+ autoload -U compinit; compinit
326
327
  eval "$(_CIRCUP_COMPLETE=zsh_source circup)"
327
328
 
328
329
  For Fish, add this to ~/.config/fish/completions/foo-bar.fish::
@@ -38,6 +38,10 @@ circup.egg-info/requires.txt
38
38
  circup.egg-info/top_level.txt
39
39
  circup/config/bundle_config.json
40
40
  circup/config/bundle_config.json.license
41
+ circup/wwshell/README.rst
42
+ circup/wwshell/README.rst.license
43
+ circup/wwshell/__init__.py
44
+ circup/wwshell/commands.py
41
45
  docs/conf.py
42
46
  docs/index.rst
43
47
  docs/index.rst.license
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  circup = circup:main
3
+ wwshell = circup.wwshell:main
@@ -109,7 +109,6 @@ if not on_rtd: # only import and set the theme if we're building docs locally
109
109
  import sphinx_rtd_theme
110
110
 
111
111
  html_theme = "sphinx_rtd_theme"
112
- html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."]
113
112
  except:
114
113
  html_theme = "default"
115
114
  html_theme_path = ["."]
@@ -1,10 +1,12 @@
1
- .. CircUp documentation master file, created by
1
+ .. Circup documentation master file, created by
2
2
  sphinx-quickstart on Mon Sep 2 10:58:36 2019.
3
3
  You can adapt this file completely to your liking, but it should at least
4
4
  contain the root `toctree` directive.
5
5
 
6
6
  .. include:: ../README.rst
7
7
 
8
+ .. include:: ../circup/wwshell/README.rst
9
+
8
10
  .. include:: ../CONTRIBUTING.rst
9
11
 
10
12
  API
@@ -42,6 +42,7 @@ optional-dependencies = {optional = {file = ["optional_requirements.txt"]}}
42
42
 
43
43
  [project.scripts]
44
44
  circup = "circup:main"
45
+ wwshell = "circup.wwshell:main"
45
46
 
46
47
  [project.urls]
47
48
  homepage = "https://github.com/adafruit/circup"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes