circup 1.1.4__tar.gz → 1.2.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 (65) hide show
  1. {circup-1.1.4 → circup-1.2.0}/.pre-commit-config.yaml +4 -3
  2. {circup-1.1.4 → circup-1.2.0}/.pylintrc +5 -42
  3. {circup-1.1.4/circup.egg-info → circup-1.2.0}/PKG-INFO +1 -1
  4. {circup-1.1.4 → circup-1.2.0}/circup/__init__.py +99 -61
  5. {circup-1.1.4 → circup-1.2.0/circup.egg-info}/PKG-INFO +1 -1
  6. {circup-1.1.4 → circup-1.2.0}/circup.egg-info/SOURCES.txt +1 -0
  7. circup-1.2.0/tests/bad_python.py +6 -0
  8. {circup-1.1.4 → circup-1.2.0}/tests/test_circup.py +31 -16
  9. {circup-1.1.4 → circup-1.2.0}/.github/ISSUE_TEMPLATE.md +0 -0
  10. {circup-1.1.4 → circup-1.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  11. {circup-1.1.4 → circup-1.2.0}/.github/workflows/build.yml +0 -0
  12. {circup-1.1.4 → circup-1.2.0}/.github/workflows/release.yml +0 -0
  13. {circup-1.1.4 → circup-1.2.0}/.gitignore +0 -0
  14. {circup-1.1.4 → circup-1.2.0}/CODE_OF_CONDUCT.rst +0 -0
  15. {circup-1.1.4 → circup-1.2.0}/CODE_OF_CONDUCT.rst.license +0 -0
  16. {circup-1.1.4 → circup-1.2.0}/CONTRIBUTING.rst +0 -0
  17. {circup-1.1.4 → circup-1.2.0}/CONTRIBUTING.rst.license +0 -0
  18. {circup-1.1.4 → circup-1.2.0}/LICENSE +0 -0
  19. {circup-1.1.4 → circup-1.2.0}/LICENSES/CC-BY-4.0.txt +0 -0
  20. {circup-1.1.4 → circup-1.2.0}/LICENSES/MIT.txt +0 -0
  21. {circup-1.1.4 → circup-1.2.0}/LICENSES/Unlicense.txt +0 -0
  22. {circup-1.1.4 → circup-1.2.0}/README.rst +0 -0
  23. {circup-1.1.4 → circup-1.2.0}/README.rst.license +0 -0
  24. {circup-1.1.4 → circup-1.2.0}/circup/config/bundle_config.json +0 -0
  25. {circup-1.1.4 → circup-1.2.0}/circup/config/bundle_config.json.license +0 -0
  26. {circup-1.1.4 → circup-1.2.0}/circup.egg-info/dependency_links.txt +0 -0
  27. {circup-1.1.4 → circup-1.2.0}/circup.egg-info/entry_points.txt +0 -0
  28. {circup-1.1.4 → circup-1.2.0}/circup.egg-info/requires.txt +6 -6
  29. {circup-1.1.4 → circup-1.2.0}/circup.egg-info/top_level.txt +0 -0
  30. {circup-1.1.4 → circup-1.2.0}/docs/_static/favicon.ico +0 -0
  31. {circup-1.1.4 → circup-1.2.0}/docs/_static/favicon.ico.license +0 -0
  32. {circup-1.1.4 → circup-1.2.0}/docs/conf.py +0 -0
  33. {circup-1.1.4 → circup-1.2.0}/docs/index.rst +0 -0
  34. {circup-1.1.4 → circup-1.2.0}/docs/index.rst.license +0 -0
  35. {circup-1.1.4 → circup-1.2.0}/docs/logo.png +0 -0
  36. {circup-1.1.4 → circup-1.2.0}/docs/logo.png.license +0 -0
  37. {circup-1.1.4 → circup-1.2.0}/readthedocs.yml +0 -0
  38. {circup-1.1.4 → circup-1.2.0}/requirements.txt +0 -0
  39. {circup-1.1.4 → circup-1.2.0}/requirements.txt.license +0 -0
  40. {circup-1.1.4 → circup-1.2.0}/setup.cfg +0 -0
  41. {circup-1.1.4 → circup-1.2.0}/setup.py +0 -0
  42. {circup-1.1.4 → circup-1.2.0}/tests/__init__.py +0 -0
  43. {circup-1.1.4 → circup-1.2.0}/tests/bad_module/__init__.py +0 -0
  44. {circup-1.1.4 → circup-1.2.0}/tests/bad_module/my_module.py +0 -0
  45. {circup-1.1.4 → circup-1.2.0}/tests/bundle.json +0 -0
  46. {circup-1.1.4 → circup-1.2.0}/tests/bundle.json.license +0 -0
  47. {circup-1.1.4 → circup-1.2.0}/tests/device.json +0 -0
  48. {circup-1.1.4 → circup-1.2.0}/tests/device.json.license +0 -0
  49. {circup-1.1.4 → circup-1.2.0}/tests/dir_module/__init__.py +0 -0
  50. {circup-1.1.4 → circup-1.2.0}/tests/dir_module/my_module.py +0 -0
  51. {circup-1.1.4 → circup-1.2.0}/tests/import_styles.py +0 -0
  52. {circup-1.1.4 → circup-1.2.0}/tests/local_module.py +0 -0
  53. {circup-1.1.4 → circup-1.2.0}/tests/local_module_cp7.mpy +0 -0
  54. {circup-1.1.4 → circup-1.2.0}/tests/local_module_cp7.mpy.license +0 -0
  55. {circup-1.1.4 → circup-1.2.0}/tests/mount_exists.txt +0 -0
  56. {circup-1.1.4 → circup-1.2.0}/tests/mount_exists.txt.license +0 -0
  57. {circup-1.1.4 → circup-1.2.0}/tests/mount_missing.txt +0 -0
  58. {circup-1.1.4 → circup-1.2.0}/tests/mount_missing.txt.license +0 -0
  59. {circup-1.1.4 → circup-1.2.0}/tests/remote_module.py +0 -0
  60. {circup-1.1.4 → circup-1.2.0}/tests/test_bundle_config.json +0 -0
  61. {circup-1.1.4 → circup-1.2.0}/tests/test_bundle_config.json.license +0 -0
  62. {circup-1.1.4 → circup-1.2.0}/tests/test_bundle_config_local.json +0 -0
  63. {circup-1.1.4 → circup-1.2.0}/tests/test_bundle_config_local.json.license +0 -0
  64. {circup-1.1.4 → circup-1.2.0}/tests/test_module.mpy +0 -0
  65. {circup-1.1.4 → circup-1.2.0}/tests/test_module.mpy.license +0 -0
@@ -4,7 +4,7 @@
4
4
 
5
5
  repos:
6
6
  - repo: https://github.com/pycqa/pylint
7
- rev: pylint-2.6.0
7
+ rev: v2.15.5
8
8
  hooks:
9
9
  - id: pylint
10
10
  name: lint (examples)
@@ -15,11 +15,12 @@ repos:
15
15
  - id: pylint
16
16
  name: lint (code)
17
17
  types: [python]
18
- exclude: "^(docs/|examples/|setup.py$)"
18
+ exclude: "^(docs/|examples/|setup.py$|tests/bad_python.py$)"
19
19
  - repo: https://github.com/python/black
20
20
  rev: 22.3.0
21
21
  hooks:
22
- - id: black
22
+ - id: black
23
+ exclude: "^tests/bad_python.py$"
23
24
  - repo: https://github.com/fsfe/reuse-tool
24
25
  rev: v0.14.0
25
26
  hooks:
@@ -8,11 +8,11 @@
8
8
  # run arbitrary code
9
9
  extension-pkg-whitelist=
10
10
 
11
- # Add files or directories to the blacklist. They should be base names, not
11
+ # Add files or directories to the ignore-list. They should be base names, not
12
12
  # paths.
13
13
  ignore=CVS
14
14
 
15
- # Add files or directories matching the regex patterns to the blacklist. The
15
+ # Add files or directories matching the regex patterns to the ignore-list. The
16
16
  # regex matches against base names, not paths.
17
17
  ignore-patterns=
18
18
 
@@ -54,8 +54,8 @@ confidence=
54
54
  # --enable=similarities". If you want to run only the classes checker, but have
55
55
  # no Warning level messages displayed, use"--disable=all --enable=classes
56
56
  # --disable=W"
57
- # disable=import-error,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call
58
- disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,import-error,attribute-defined-outside-init,bad-continuation,invalid-name, redefined-builtin, exec-used, global-statement, too-many-lines
57
+
58
+ disable=too-many-lines, consider-using-f-string, use-dict-literal, global-statement, invalid-name, fixme, import-error
59
59
 
60
60
  # Enable the message, report, category or checker with the given id(s). You can
61
61
  # either give multiple identifier separated by comma (,) or put this option
@@ -225,12 +225,6 @@ max-line-length=100
225
225
  # Maximum number of lines in a module
226
226
  max-module-lines=1000
227
227
 
228
- # List of optional constructs for which whitespace checking is disabled. `dict-
229
- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
230
- # `trailing-comma` allows a space between comma and closing bracket: (a, ).
231
- # `empty-line` allows space-only lines.
232
- no-space-check=trailing-comma,dict-separator
233
-
234
228
  # Allow the body of a class to be on the same line as the declaration if body
235
229
  # contains single statement.
236
230
  single-line-class-stmt=no
@@ -249,7 +243,7 @@ ignore-comments=yes
249
243
  ignore-docstrings=yes
250
244
 
251
245
  # Ignore imports when computing similarities.
252
- ignore-imports=no
246
+ ignore-imports=yes
253
247
 
254
248
  # Minimum lines number of a similarity.
255
249
  min-similarity-lines=4
@@ -257,38 +251,22 @@ min-similarity-lines=4
257
251
 
258
252
  [BASIC]
259
253
 
260
- # Naming hint for argument names
261
- argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
262
-
263
254
  # Regular expression matching correct argument names
264
255
  argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
265
256
 
266
- # Naming hint for attribute names
267
- attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
268
-
269
257
  # Regular expression matching correct attribute names
270
258
  attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
271
259
 
272
260
  # Bad variable names which should always be refused, separated by a comma
273
261
  bad-names=foo,bar,baz,toto,tutu,tata
274
262
 
275
- # Naming hint for class attribute names
276
- class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
277
-
278
263
  # Regular expression matching correct class attribute names
279
264
  class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
280
265
 
281
- # Naming hint for class names
282
- # class-name-hint=[A-Z_][a-zA-Z0-9]+$
283
- class-name-hint=[A-Z_][a-zA-Z0-9_]+$
284
-
285
266
  # Regular expression matching correct class names
286
267
  # class-rgx=[A-Z_][a-zA-Z0-9]+$
287
268
  class-rgx=[A-Z_][a-zA-Z0-9_]+$
288
269
 
289
- # Naming hint for constant names
290
- const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
291
-
292
270
  # Regular expression matching correct constant names
293
271
  const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
294
272
 
@@ -296,9 +274,6 @@ const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
296
274
  # ones are exempt.
297
275
  docstring-min-length=-1
298
276
 
299
- # Naming hint for function names
300
- function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
301
-
302
277
  # Regular expression matching correct function names
303
278
  function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
304
279
 
@@ -309,21 +284,12 @@ good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_
309
284
  # Include a hint for the correct naming format with invalid-name
310
285
  include-naming-hint=no
311
286
 
312
- # Naming hint for inline iteration names
313
- inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
314
-
315
287
  # Regular expression matching correct inline iteration names
316
288
  inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
317
289
 
318
- # Naming hint for method names
319
- method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
320
-
321
290
  # Regular expression matching correct method names
322
291
  method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
323
292
 
324
- # Naming hint for module names
325
- module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
326
-
327
293
  # Regular expression matching correct module names
328
294
  module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
329
295
 
@@ -339,9 +305,6 @@ no-docstring-rgx=^_
339
305
  # to this list to register other decorators that produce valid properties.
340
306
  property-classes=abc.abstractproperty
341
307
 
342
- # Naming hint for variable names
343
- variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
344
-
345
308
  # Regular expression matching correct variable names
346
309
  variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$
347
310
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: circup
3
- Version: 1.1.4
3
+ Version: 1.2.0
4
4
  Summary: A tool to manage/update libraries on CircuitPython devices.
5
5
  Home-page: https://github.com/adafruit/circup
6
6
  Author: Adafruit Industries
@@ -63,6 +63,8 @@ PLATFORMS = {"py": "py", "7mpy": "7.x-mpy", "8mpy": "7.x-mpy"}
63
63
  BOARDLESS_COMMANDS = ["show", "bundle-add", "bundle-remove", "bundle-show"]
64
64
  #: Version identifier for a bad MPY file format
65
65
  BAD_FILE_FORMAT = "Invalid"
66
+ #: Timeout for requests calls like get()
67
+ REQUESTS_TIMEOUT = 30
66
68
 
67
69
  # Ensure DATA_DIR / LOG_DIR related directories and files exist.
68
70
  if not os.path.exists(DATA_DIR): # pragma: no cover
@@ -143,7 +145,7 @@ class Bundle:
143
145
  "requirements.txt",
144
146
  )
145
147
  if os.path.isfile(requirements_txt):
146
- with open(requirements_txt, "r") as read_this:
148
+ with open(requirements_txt, "r", encoding="utf-8") as read_this:
147
149
  return read_this.read()
148
150
  return None
149
151
 
@@ -190,7 +192,7 @@ class Bundle:
190
192
  return False
191
193
  for platform in PLATFORMS.values():
192
194
  url = self.url_format.format(platform=platform, tag=tag)
193
- r = requests.get(url, stream=True)
195
+ r = requests.get(url, stream=True, timeout=REQUESTS_TIMEOUT)
194
196
  # pylint: disable=no-member
195
197
  if r.status_code != requests.codes.ok:
196
198
  if VERBOSE:
@@ -438,7 +440,7 @@ def clean_library_name(assumed_library_name):
438
440
  .replace("_circuitpython_", "_")
439
441
  .replace("-", "_")
440
442
  )
441
- if assumed_library_name in not_standard_names.keys():
443
+ if assumed_library_name in not_standard_names:
442
444
  return not_standard_names[assumed_library_name]
443
445
  return assumed_library_name
444
446
 
@@ -516,7 +518,7 @@ def extract_metadata(path):
516
518
  logger.info("%s", path)
517
519
  if path.endswith(".py"):
518
520
  result["mpy"] = False
519
- with open(path, encoding="utf-8") as source_file:
521
+ with open(path, "r", encoding="utf-8") as source_file:
520
522
  content = source_file.read()
521
523
  #: The regex used to extract ``__version__`` and ``__repo__`` assignments.
522
524
  dunder_key_val = r"""(__\w+__)(?:\s*:\s*\w+)?\s*=\s*(?:['"]|\(\s)(.+)['"]"""
@@ -692,7 +694,7 @@ def get_bundle(bundle, tag):
692
694
  for platform, github_string in PLATFORMS.items():
693
695
  url = bundle.url_format.format(platform=github_string, tag=tag)
694
696
  logger.info("Downloading bundle: %s", url)
695
- r = requests.get(url, stream=True)
697
+ r = requests.get(url, stream=True, timeout=REQUESTS_TIMEOUT)
696
698
  # pylint: disable=no-member
697
699
  if r.status_code != requests.codes.ok:
698
700
  logger.warning("Unable to connect to %s", url)
@@ -702,9 +704,9 @@ def get_bundle(bundle, tag):
702
704
  temp_zip = bundle.zip.format(platform=platform)
703
705
  with click.progressbar(r.iter_content(1024), length=total_size) as pbar, open(
704
706
  temp_zip, "wb"
705
- ) as f:
707
+ ) as zip_fp:
706
708
  for chunk in pbar:
707
- f.write(chunk)
709
+ zip_fp.write(chunk)
708
710
  pbar.update(len(chunk))
709
711
  logger.info("Saved to %s", temp_zip)
710
712
  temp_dir = bundle.dir.format(platform=platform)
@@ -750,10 +752,10 @@ def get_bundles_dict():
750
752
  """
751
753
  bundle_dict = get_bundles_local_dict()
752
754
  try:
753
- with open(BUNDLE_CONFIG_OVERWRITE) as bundle_config_json:
755
+ with open(BUNDLE_CONFIG_OVERWRITE, "rb") as bundle_config_json:
754
756
  bundle_config = json.load(bundle_config_json)
755
757
  except (FileNotFoundError, json.decoder.JSONDecodeError):
756
- with open(BUNDLE_CONFIG_FILE) as bundle_config_json:
758
+ with open(BUNDLE_CONFIG_FILE, "rb") as bundle_config_json:
757
759
  bundle_config = json.load(bundle_config_json)
758
760
  for name, bundle in bundle_config.items():
759
761
  if bundle not in bundle_dict.values():
@@ -768,7 +770,7 @@ def get_bundles_local_dict():
768
770
  :return: Raw dictionary from the config file(s).
769
771
  """
770
772
  try:
771
- with open(BUNDLE_CONFIG_LOCAL) as bundle_config_json:
773
+ with open(BUNDLE_CONFIG_LOCAL, "rb") as bundle_config_json:
772
774
  bundle_config = json.load(bundle_config_json)
773
775
  if not isinstance(bundle_config, dict) or not bundle_config:
774
776
  logger.error("Local bundle list invalid. Skipped.")
@@ -807,7 +809,9 @@ def get_circuitpython_version(device_path):
807
809
  :return: A tuple with the version string for CircuitPython and the board ID string.
808
810
  """
809
811
  try:
810
- with open(os.path.join(device_path, "boot_out.txt")) as boot:
812
+ with open(
813
+ os.path.join(device_path, "boot_out.txt"), "r", encoding="utf-8"
814
+ ) as boot:
811
815
  version_line = boot.readline()
812
816
  circuit_python = version_line.split(";")[0].split(" ")[-3]
813
817
  board_line = boot.readline()
@@ -885,7 +889,7 @@ def get_dependencies(*requested_libraries, mod_names, to_install=()):
885
889
  # If nothing is requested, we're done
886
890
  return _to_install
887
891
 
888
- for library in _requested_libraries:
892
+ for library in list(_requested_libraries):
889
893
  if library not in _to_install:
890
894
  _to_install = _to_install + (library,)
891
895
  # get the requirements.txt from bundle
@@ -923,7 +927,7 @@ def get_latest_release_from_url(url):
923
927
  """
924
928
 
925
929
  logger.info("Requesting redirect information: %s", url)
926
- response = requests.head(url)
930
+ response = requests.head(url, timeout=REQUESTS_TIMEOUT)
927
931
  responseurl = response.url
928
932
  if response.is_redirect:
929
933
  responseurl = response.headers["Location"]
@@ -945,7 +949,7 @@ def get_modules(path):
945
949
  return result
946
950
  single_file_py_mods = glob.glob(os.path.join(path, "*.py"))
947
951
  single_file_mpy_mods = glob.glob(os.path.join(path, "*.mpy"))
948
- directory_mods = [
952
+ package_dir_mods = [
949
953
  d
950
954
  for d in glob.glob(os.path.join(path, "*", ""))
951
955
  if not os.path.basename(os.path.normpath(d)).startswith(".")
@@ -955,18 +959,18 @@ def get_modules(path):
955
959
  metadata = extract_metadata(sfm)
956
960
  metadata["path"] = sfm
957
961
  result[os.path.basename(sfm).replace(".py", "").replace(".mpy", "")] = metadata
958
- for dm in directory_mods:
959
- name = os.path.basename(os.path.dirname(dm))
960
- py_files = glob.glob(os.path.join(dm, "*.py"))
961
- mpy_files = glob.glob(os.path.join(dm, "*.mpy"))
962
+ for package_path in package_dir_mods:
963
+ name = os.path.basename(os.path.dirname(package_path))
964
+ py_files = glob.glob(os.path.join(package_path, "**/*.py"), recursive=True)
965
+ mpy_files = glob.glob(os.path.join(package_path, "**/*.mpy"), recursive=True)
962
966
  all_files = py_files + mpy_files
963
967
  # default value
964
- result[name] = {"path": dm, "mpy": bool(mpy_files)}
968
+ result[name] = {"path": package_path, "mpy": bool(mpy_files)}
965
969
  # explore all the submodules to detect bad ones
966
970
  for source in [f for f in all_files if not os.path.basename(f).startswith(".")]:
967
971
  metadata = extract_metadata(source)
968
972
  if "__version__" in metadata:
969
- metadata["path"] = dm
973
+ metadata["path"] = package_path
970
974
  result[name] = metadata
971
975
  # break now if any of the submodules has a bad format
972
976
  if metadata["__version__"] == BAD_FILE_FORMAT:
@@ -976,7 +980,7 @@ def get_modules(path):
976
980
 
977
981
  # pylint: disable=too-many-locals,too-many-branches
978
982
  def install_module(
979
- device_path, device_modules, name, py, mod_names
983
+ device_path, device_modules, name, pyext, mod_names
980
984
  ): # pragma: no cover
981
985
  """
982
986
  Finds a connected device and installs a given module name if it
@@ -987,7 +991,7 @@ def install_module(
987
991
  :param str device_path: The path to the connected board.
988
992
  :param list(dict) device_modules: List of module metadata from the device.
989
993
  :param str name: Name of module to install
990
- :param bool py: Boolean to specify if the module should be installed from
994
+ :param bool pyext: Boolean to specify if the module should be installed from
991
995
  source or from a pre-compiled module
992
996
  :param mod_names: Dictionary of metadata from modules that can be generated
993
997
  with get_bundle_versions()
@@ -1004,7 +1008,7 @@ def install_module(
1004
1008
  if name in device_modules:
1005
1009
  click.echo("'{}' is already installed.".format(name))
1006
1010
  return
1007
- if py:
1011
+ if pyext:
1008
1012
  # Use Python source for module.
1009
1013
  source_path = metadata["path"] # Path to Python source version.
1010
1014
  if os.path.isdir(source_path):
@@ -1052,7 +1056,15 @@ def libraries_from_imports(code_py, mod_names):
1052
1056
  :param str code_py: Full path of the code.py file
1053
1057
  :return: sequence of library names
1054
1058
  """
1055
- imports = [info.name.split(".", 1)[0] for info in findimports.find_imports(code_py)]
1059
+ # pylint: disable=broad-except
1060
+ try:
1061
+ found_imports = findimports.find_imports(code_py)
1062
+ except Exception as ex: # broad exception because anything could go wrong
1063
+ logger.exception(ex)
1064
+ click.secho('Unable to read the auto file: "{}"'.format(str(ex)), fg="red")
1065
+ sys.exit(2)
1066
+ # pylint: enable=broad-except
1067
+ imports = [info.name.split(".", 1)[0] for info in found_imports]
1056
1068
  return [r for r in imports if r in mod_names]
1057
1069
 
1058
1070
 
@@ -1232,7 +1244,9 @@ def freeze(ctx, requirement): # pragma: no cover
1232
1244
  cwd = os.path.abspath(os.getcwd())
1233
1245
  for i, module in enumerate(output):
1234
1246
  output[i] += "\n"
1235
- with open(cwd + "/" + "requirements.txt", "w", newline="\n") as file:
1247
+ with open(
1248
+ cwd + "/" + "requirements.txt", "w", newline="\n", encoding="utf-8"
1249
+ ) as file:
1236
1250
  file.truncate(0)
1237
1251
  file.writelines(output)
1238
1252
  else:
@@ -1284,23 +1298,32 @@ def list_cli(ctx): # pragma: no cover
1284
1298
  @click.argument(
1285
1299
  "modules", required=False, nargs=-1, shell_complete=completion_for_install
1286
1300
  )
1287
- @click.option("--py", is_flag=True)
1288
- @click.option("-r", "--requirement", type=click.Path(exists=True, dir_okay=False))
1289
- @click.option("--auto/--no-auto", "-a/-A")
1290
- @click.option("--auto-file", default="code.py")
1301
+ @click.option(
1302
+ "pyext",
1303
+ "--py",
1304
+ is_flag=True,
1305
+ help="Install the .py version of the module(s) instead of the mpy version.",
1306
+ )
1307
+ @click.option(
1308
+ "-r",
1309
+ "--requirement",
1310
+ type=click.Path(exists=True, dir_okay=False),
1311
+ help="specify a text file to install all modules listed in the text file."
1312
+ " Typically requirements.txt.",
1313
+ )
1314
+ @click.option("--auto", "-a", help="Install the modules imported in code.py.")
1315
+ @click.option(
1316
+ "--auto-file",
1317
+ default=None,
1318
+ help="Specify the name of a file on the board to read for auto install."
1319
+ " Also accepts an absolute path or a local ./ path.",
1320
+ )
1291
1321
  @click.pass_context
1292
- def install(ctx, modules, py, requirement, auto, auto_file): # pragma: no cover
1322
+ def install(ctx, modules, pyext, requirement, auto, auto_file): # pragma: no cover
1293
1323
  """
1294
1324
  Install a named module(s) onto the device. Multiple modules
1295
1325
  can be installed at once by providing more than one module name, each
1296
1326
  separated by a space.
1297
-
1298
- Option --py installs .py version of module(s).
1299
-
1300
- Option -r allows specifying a text file to install all modules listed in
1301
- the text file.
1302
-
1303
- Option -a installs based on the modules imported by code.py
1304
1327
  """
1305
1328
  # TODO: Ensure there's enough space on the device
1306
1329
  available_modules = get_bundle_versions(get_bundles_list())
@@ -1308,11 +1331,19 @@ def install(ctx, modules, py, requirement, auto, auto_file): # pragma: no cover
1308
1331
  for module, metadata in available_modules.items():
1309
1332
  mod_names[module.replace(".py", "").lower()] = metadata
1310
1333
  if requirement:
1311
- with open(requirement, "r") as fp:
1312
- requirements_txt = fp.read()
1334
+ with open(requirement, "r", encoding="utf-8") as rfile:
1335
+ requirements_txt = rfile.read()
1313
1336
  requested_installs = libraries_from_requirements(requirements_txt)
1314
- elif auto:
1315
- auto_file = os.path.join(ctx.obj["DEVICE_PATH"], auto_file)
1337
+ elif auto or auto_file:
1338
+ if auto_file is None:
1339
+ auto_file = "code.py"
1340
+ # pass a local file with "./" or "../"
1341
+ is_relative = auto_file.split(os.sep)[0] in [os.path.curdir, os.path.pardir]
1342
+ if not os.path.isabs(auto_file) and not is_relative:
1343
+ auto_file = os.path.join(ctx.obj["DEVICE_PATH"], auto_file or "code.py")
1344
+ if not os.path.isfile(auto_file):
1345
+ click.secho(f"Auto file not found: {auto_file}", fg="red")
1346
+ sys.exit(1)
1316
1347
  requested_installs = libraries_from_imports(auto_file, mod_names)
1317
1348
  else:
1318
1349
  requested_installs = modules
@@ -1325,7 +1356,7 @@ def install(ctx, modules, py, requirement, auto, auto_file): # pragma: no cover
1325
1356
  click.echo(f"Ready to install: {to_install}\n")
1326
1357
  for library in to_install:
1327
1358
  install_module(
1328
- ctx.obj["DEVICE_PATH"], device_modules, library, py, mod_names
1359
+ ctx.obj["DEVICE_PATH"], device_modules, library, pyext, mod_names
1329
1360
  )
1330
1361
 
1331
1362
 
@@ -1397,10 +1428,13 @@ def uninstall(ctx, module): # pragma: no cover
1397
1428
  )
1398
1429
  )
1399
1430
  @click.option(
1400
- "--all", is_flag=True, help="Update all modules without Major Version warnings."
1431
+ "update_all",
1432
+ "--all",
1433
+ is_flag=True,
1434
+ help="Update all modules without Major Version warnings.",
1401
1435
  )
1402
1436
  @click.pass_context
1403
- def update(ctx, all): # pragma: no cover
1437
+ def update(ctx, update_all): # pragma: no cover
1404
1438
  """
1405
1439
  Checks for out-of-date modules on the connected CIRCUITPYTHON device, and
1406
1440
  prompts the user to confirm updating such modules.
@@ -1414,10 +1448,10 @@ def update(ctx, all): # pragma: no cover
1414
1448
  ]
1415
1449
  if modules:
1416
1450
  click.echo("Found {} module[s] needing update.".format(len(modules)))
1417
- if not all:
1451
+ if not update_all:
1418
1452
  click.echo("Please indicate which modules you wish to update:\n")
1419
1453
  for module in modules:
1420
- update_flag = all
1454
+ update_flag = update_all
1421
1455
  if VERBOSE:
1422
1456
  click.echo(
1423
1457
  "Device version: {}, Bundle version: {}".format(
@@ -1484,12 +1518,12 @@ def bundle_show(modules):
1484
1518
  Show the list of bundles, default and local, with URL, current version
1485
1519
  and latest version retrieved from the web.
1486
1520
  """
1487
- locals = get_bundles_local_dict().values()
1521
+ local_bundles = get_bundles_local_dict().values()
1488
1522
  bundles = get_bundles_list()
1489
1523
  available_modules = get_bundle_versions(bundles)
1490
1524
 
1491
1525
  for bundle in bundles:
1492
- if bundle.key in locals:
1526
+ if bundle.key in local_bundles:
1493
1527
  click.secho(bundle.key, fg="yellow")
1494
1528
  else:
1495
1529
  click.secho(bundle.key, fg="green")
@@ -1513,40 +1547,44 @@ def bundle_add(bundle):
1513
1547
  """
1514
1548
  bundles_dict = get_bundles_local_dict()
1515
1549
  modified = False
1516
- for bun in bundle:
1550
+ for bundle_repo in bundle:
1517
1551
  # cleanup in case seombody pastes the URL to the repo/releases
1518
- bun = re.sub(r"https?://github.com/([^/]+/[^/]+)(/.*)?", r"\1", bun)
1519
- if bun in bundles_dict.values():
1552
+ bundle_repo = re.sub(
1553
+ r"https?://github.com/([^/]+/[^/]+)(/.*)?", r"\1", bundle_repo
1554
+ )
1555
+ if bundle_repo in bundles_dict.values():
1520
1556
  click.secho("Bundle already in list.", fg="yellow")
1521
- click.secho(" " + bun, fg="yellow")
1557
+ click.secho(" " + bundle_repo, fg="yellow")
1522
1558
  continue
1523
1559
  try:
1524
- bb = Bundle(bun)
1560
+ bundle_added = Bundle(bundle_repo)
1525
1561
  except ValueError:
1526
1562
  click.secho(
1527
1563
  "Bundle string invalid, expecting github URL or `user/repository` string.",
1528
1564
  fg="red",
1529
1565
  )
1530
- click.secho(" " + bun, fg="red")
1566
+ click.secho(" " + bundle_repo, fg="red")
1531
1567
  continue
1532
- result = requests.get("https://github.com/" + bun)
1568
+ result = requests.get(
1569
+ "https://github.com/" + bundle_repo, timeout=REQUESTS_TIMEOUT
1570
+ )
1533
1571
  # pylint: disable=no-member
1534
1572
  if result.status_code == requests.codes.NOT_FOUND:
1535
1573
  click.secho("Bundle invalid, the repository doesn't exist (404).", fg="red")
1536
- click.secho(" " + bun, fg="red")
1574
+ click.secho(" " + bundle_repo, fg="red")
1537
1575
  continue
1538
1576
  # pylint: enable=no-member
1539
- if not bb.validate():
1577
+ if not bundle_added.validate():
1540
1578
  click.secho(
1541
1579
  "Bundle invalid, is the repository a valid circup bundle ?", fg="red"
1542
1580
  )
1543
- click.secho(" " + bun, fg="red")
1581
+ click.secho(" " + bundle_repo, fg="red")
1544
1582
  continue
1545
1583
  # note: use bun as the dictionary key for uniqueness
1546
- bundles_dict[bun] = bun
1584
+ bundles_dict[bundle_repo] = bundle_repo
1547
1585
  modified = True
1548
- click.echo("Added " + bun)
1549
- click.echo(" " + bb.url)
1586
+ click.echo("Added " + bundle_repo)
1587
+ click.echo(" " + bundle_added.url)
1550
1588
  if modified:
1551
1589
  # save the bundles list
1552
1590
  save_local_bundles(bundles_dict)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: circup
3
- Version: 1.1.4
3
+ Version: 1.2.0
4
4
  Summary: A tool to manage/update libraries on CircuitPython devices.
5
5
  Home-page: https://github.com/adafruit/circup
6
6
  Author: Adafruit Industries
@@ -36,6 +36,7 @@ docs/logo.png.license
36
36
  docs/_static/favicon.ico
37
37
  docs/_static/favicon.ico.license
38
38
  tests/__init__.py
39
+ tests/bad_python.py
39
40
  tests/bundle.json
40
41
  tests/bundle.json.license
41
42
  tests/device.json
@@ -0,0 +1,6 @@
1
+ # SPDX-FileCopyrightText: 2021 Jeff Epler for Adafruit Industries
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ # pylint: disable=all
5
+
6
+ if True:
@@ -1,6 +1,8 @@
1
1
  # SPDX-FileCopyrightText: 2019 Nicholas Tollervey, written for Adafruit Industries
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
+ # pylint: disable=too-many-lines,use-implicit-booleaness-not-comparison
5
+ # pylint: disable=invalid-name,unnecessary-dunder-call
4
6
  """
5
7
  Unit tests for the circup module.
6
8
 
@@ -39,12 +41,12 @@ import circup
39
41
 
40
42
 
41
43
  TEST_BUNDLE_CONFIG_JSON = "tests/test_bundle_config.json"
42
- with open(TEST_BUNDLE_CONFIG_JSON) as tbc:
44
+ with open(TEST_BUNDLE_CONFIG_JSON, "rb") as tbc:
43
45
  TEST_BUNDLE_DATA = json.load(tbc)
44
46
  TEST_BUNDLE_NAME = TEST_BUNDLE_DATA["test_bundle"]
45
47
 
46
48
  TEST_BUNDLE_CONFIG_LOCAL_JSON = "tests/test_bundle_config_local.json"
47
- with open(TEST_BUNDLE_CONFIG_LOCAL_JSON) as tbc:
49
+ with open(TEST_BUNDLE_CONFIG_LOCAL_JSON, "rb") as tbc:
48
50
  TEST_BUNDLE_LOCAL_DATA = json.load(tbc)
49
51
 
50
52
 
@@ -546,7 +548,7 @@ def test_get_latest_release_from_url():
546
548
  with mock.patch("circup.requests.head", return_value=response) as mock_get:
547
549
  result = circup.get_latest_release_from_url(expected_url)
548
550
  assert result == "20190903"
549
- mock_get.assert_called_once_with(expected_url)
551
+ mock_get.assert_called_once_with(expected_url, timeout=mock.ANY)
550
552
 
551
553
 
552
554
  def test_extract_metadata_python():
@@ -563,7 +565,7 @@ def test_extract_metadata_python():
563
565
  path = "foo.py"
564
566
  with mock.patch("builtins.open", mock.mock_open(read_data=code)) as mock_open:
565
567
  result = circup.extract_metadata(path)
566
- mock_open.assert_called_once_with(path, encoding="utf-8")
568
+ mock_open.assert_called_once_with(path, "r", encoding="utf-8")
567
569
  assert len(result) == 3
568
570
  assert result["__version__"] == "1.1.4"
569
571
  assert result["__repo__"] == "https://github.com/adafruit/SomeLibrary.git"
@@ -598,9 +600,9 @@ def test_find_modules():
598
600
  Ensure that the expected list of Module instances is returned given the
599
601
  metadata dictionary fixtures for device and bundle modules.
600
602
  """
601
- with open("tests/device.json") as f:
603
+ with open("tests/device.json", "rb") as f:
602
604
  device_modules = json.load(f)
603
- with open("tests/bundle.json") as f:
605
+ with open("tests/bundle.json", "rb") as f:
604
606
  bundle_modules = json.load(f)
605
607
  with mock.patch(
606
608
  "circup.get_device_versions", return_value=device_modules
@@ -698,7 +700,9 @@ def test_get_circuitpython_version():
698
700
  )
699
701
  with mock.patch("builtins.open", mock.mock_open(read_data=data_no_id)) as mock_open:
700
702
  assert circup.get_circuitpython_version(device_path) == ("4.1.0", "")
701
- mock_open.assert_called_once_with(os.path.join(device_path, "boot_out.txt"))
703
+ mock_open.assert_called_once_with(
704
+ os.path.join(device_path, "boot_out.txt"), "r", encoding="utf-8"
705
+ )
702
706
  data_with_id = data_no_id + "\r\n" "Board ID:this_is_a_board"
703
707
  with mock.patch(
704
708
  "builtins.open", mock.mock_open(read_data=data_with_id)
@@ -707,7 +711,9 @@ def test_get_circuitpython_version():
707
711
  "4.1.0",
708
712
  "this_is_a_board",
709
713
  )
710
- mock_open.assert_called_once_with(os.path.join(device_path, "boot_out.txt"))
714
+ mock_open.assert_called_once_with(
715
+ os.path.join(device_path, "boot_out.txt"), "r", encoding="utf-8"
716
+ )
711
717
 
712
718
 
713
719
  def test_get_device_versions():
@@ -961,7 +967,7 @@ def test_get_bundle_network_error():
961
967
  "https://github.com/" + TEST_BUNDLE_NAME + "/releases/download"
962
968
  "/{tag}/adafruit-circuitpython-bundle-py-{tag}.zip".format(tag=tag)
963
969
  )
964
- mock_requests.get.assert_called_once_with(url, stream=True)
970
+ mock_requests.get.assert_called_once_with(url, stream=True, timeout=mock.ANY)
965
971
  assert mock_logger.warning.call_count == 1
966
972
  mock_requests.get().raise_for_status.assert_called_once_with()
967
973
 
@@ -971,11 +977,11 @@ def test_show_command():
971
977
  test_show_command
972
978
  """
973
979
  runner = CliRunner()
974
- TEST_BUNDLE_MODULES = ["one.py", "two.py", "three.py"]
975
- with mock.patch("circup.get_bundle_versions", return_value=TEST_BUNDLE_MODULES):
980
+ test_bundle_modules = ["one.py", "two.py", "three.py"]
981
+ with mock.patch("circup.get_bundle_versions", return_value=test_bundle_modules):
976
982
  result = runner.invoke(circup.show)
977
983
  assert result.exit_code == 0
978
- assert all([m.replace(".py", "") in result.output for m in TEST_BUNDLE_MODULES])
984
+ assert all(m.replace(".py", "") in result.output for m in test_bundle_modules)
979
985
 
980
986
 
981
987
  def test_show_match_command():
@@ -983,8 +989,8 @@ def test_show_match_command():
983
989
  test_show_match_command
984
990
  """
985
991
  runner = CliRunner()
986
- TEST_BUNDLE_MODULES = ["one.py", "two.py", "three.py"]
987
- with mock.patch("circup.get_bundle_versions", return_value=TEST_BUNDLE_MODULES):
992
+ test_bundle_modules = ["one.py", "two.py", "three.py"]
993
+ with mock.patch("circup.get_bundle_versions", return_value=test_bundle_modules):
988
994
  result = runner.invoke(circup.show, ["t"])
989
995
  assert result.exit_code == 0
990
996
  assert "one" not in result.output
@@ -995,8 +1001,8 @@ def test_show_match_py_command():
995
1001
  Check that py does not match the .py extention in the module names
996
1002
  """
997
1003
  runner = CliRunner()
998
- TEST_BUNDLE_MODULES = ["one.py", "two.py", "three.py"]
999
- with mock.patch("circup.get_bundle_versions", return_value=TEST_BUNDLE_MODULES):
1004
+ test_bundle_modules = ["one.py", "two.py", "three.py"]
1005
+ with mock.patch("circup.get_bundle_versions", return_value=test_bundle_modules):
1000
1006
  result = runner.invoke(circup.show, ["py"])
1001
1007
  assert result.exit_code == 0
1002
1008
  assert "0 shown" in result.output
@@ -1024,3 +1030,12 @@ def test_libraries_from_imports():
1024
1030
  "adafruit_esp32spi",
1025
1031
  "adafruit_hid",
1026
1032
  ]
1033
+
1034
+
1035
+ def test_libraries_from_imports_bad():
1036
+ """Ensure that we catch an import error"""
1037
+ TEST_BUNDLE_MODULES = {"one.py": {}, "two.py": {}, "three.py": {}}
1038
+ runner = CliRunner()
1039
+ with mock.patch("circup.get_bundle_versions", return_value=TEST_BUNDLE_MODULES):
1040
+ result = runner.invoke(circup.install, ["--auto-file", "./tests/bad_python.py"])
1041
+ assert result.exit_code == 2
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
@@ -9,15 +9,15 @@ update_checker
9
9
  importlib_metadata
10
10
 
11
11
  [all]
12
- black
13
- pytest-random-order>=1.0.0
12
+ sphinx
14
13
  pytest-cov
15
- wheel
16
- twine
14
+ coverage
15
+ pytest-random-order>=1.0.0
16
+ black
17
17
  pytest
18
18
  pylint
19
- sphinx
20
- coverage
19
+ twine
20
+ wheel
21
21
  pytest-faulthandler
22
22
 
23
23
  [dev]
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