circup 2.2.3__tar.gz → 2.2.5__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.
- {circup-2.2.3 → circup-2.2.5}/.gitignore +3 -0
- {circup-2.2.3/circup.egg-info → circup-2.2.5}/PKG-INFO +1 -1
- {circup-2.2.3 → circup-2.2.5}/circup/command_utils.py +12 -0
- {circup-2.2.3 → circup-2.2.5}/circup/commands.py +22 -0
- circup-2.2.5/circup/lazy_metadata.py +113 -0
- {circup-2.2.3 → circup-2.2.5}/circup/shared.py +27 -15
- {circup-2.2.3 → circup-2.2.5/circup.egg-info}/PKG-INFO +1 -1
- {circup-2.2.3 → circup-2.2.5}/circup.egg-info/SOURCES.txt +1 -0
- {circup-2.2.3 → circup-2.2.5}/.github/ISSUE_TEMPLATE.md +0 -0
- {circup-2.2.3 → circup-2.2.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {circup-2.2.3 → circup-2.2.5}/.github/workflows/build.yml +0 -0
- {circup-2.2.3 → circup-2.2.5}/.github/workflows/release.yml +0 -0
- {circup-2.2.3 → circup-2.2.5}/.isort.cfg +0 -0
- {circup-2.2.3 → circup-2.2.5}/.pre-commit-config.yaml +0 -0
- {circup-2.2.3 → circup-2.2.5}/.pylintrc +0 -0
- {circup-2.2.3 → circup-2.2.5}/CODE_OF_CONDUCT.rst +0 -0
- {circup-2.2.3 → circup-2.2.5}/CODE_OF_CONDUCT.rst.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/CONTRIBUTING.rst +0 -0
- {circup-2.2.3 → circup-2.2.5}/CONTRIBUTING.rst.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/LICENSE +0 -0
- {circup-2.2.3 → circup-2.2.5}/LICENSES/CC-BY-4.0.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/LICENSES/MIT.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/LICENSES/Unlicense.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/README.rst +0 -0
- {circup-2.2.3 → circup-2.2.5}/README.rst.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup/__init__.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup/backends.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup/bundle.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup/config/bundle_config.json +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup/config/bundle_config.json.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup/logging.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup/module.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup/wwshell/README.rst +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup/wwshell/README.rst.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup/wwshell/__init__.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup/wwshell/commands.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup.egg-info/dependency_links.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup.egg-info/entry_points.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup.egg-info/requires.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/circup.egg-info/top_level.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/docs/_static/favicon.ico +0 -0
- {circup-2.2.3 → circup-2.2.5}/docs/_static/favicon.ico.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/docs/conf.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/docs/index.rst +0 -0
- {circup-2.2.3 → circup-2.2.5}/docs/index.rst.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/docs/logo.png +0 -0
- {circup-2.2.3 → circup-2.2.5}/docs/logo.png.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/optional_requirements.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/optional_requirements.txt.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/pyproject.toml +0 -0
- {circup-2.2.3 → circup-2.2.5}/readthedocs.yml +0 -0
- {circup-2.2.3 → circup-2.2.5}/requirements.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/requirements.txt.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/setup.cfg +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/__init__.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/bad_module/__init__.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/bad_module/my_module.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/bad_python.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/bundle.json +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/bundle.json.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/device.json +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/device.json.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/dir_module/__init__.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/dir_module/my_module.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/import_styles.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/local_module.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/local_module_cp7.mpy +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/local_module_cp7.mpy.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device/apps/test_app/import_styles.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device/apps/test_app/import_styles_sub.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device/boot_out.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device/boot_out.txt.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device/import_styles.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device/import_styles_sub.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device/lib/adafruit_waveform/.gitkeep +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device_2/.gitignore +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device_2/boot_out.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device_2/boot_out.txt.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device_2/code.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device_2/import_styles_sub.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device_2/package/__init__.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mock_device_2/package/other.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mount_exists.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mount_exists.txt.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mount_missing.txt +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/mount_missing.txt.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/remote_module.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/test_bundle_config.json +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/test_bundle_config.json.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/test_bundle_config_local.json +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/test_bundle_config_local.json.license +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/test_circup.py +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/test_module.mpy +0 -0
- {circup-2.2.3 → circup-2.2.5}/tests/test_module.mpy.license +0 -0
|
@@ -841,3 +841,15 @@ def sorted_by_directory_then_alpha(list_of_files):
|
|
|
841
841
|
sorted_full_list.append(files[cur_name])
|
|
842
842
|
|
|
843
843
|
return sorted_full_list
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def is_virtual_env_active():
|
|
847
|
+
"""
|
|
848
|
+
Check if a virtual environment is currently active.
|
|
849
|
+
|
|
850
|
+
We don't check the more commonly recommended way of checking if
|
|
851
|
+
sys.prefix != sys.base_prefix as this will always be true if running circup
|
|
852
|
+
from a pipx install. This way ensures the user manually activated a
|
|
853
|
+
virtual environment, regardless how circup is installed.
|
|
854
|
+
"""
|
|
855
|
+
return "VIRTUAL_ENV" in os.environ
|
|
@@ -41,6 +41,7 @@ from circup.command_utils import (
|
|
|
41
41
|
get_bundles_dict,
|
|
42
42
|
completion_for_example,
|
|
43
43
|
get_bundle_examples,
|
|
44
|
+
is_virtual_env_active,
|
|
44
45
|
)
|
|
45
46
|
|
|
46
47
|
|
|
@@ -354,6 +355,7 @@ def install(
|
|
|
354
355
|
device_modules = ctx.obj["backend"].get_device_versions()
|
|
355
356
|
if to_install is not None:
|
|
356
357
|
to_install = sorted(to_install)
|
|
358
|
+
is_global_install_ok = None
|
|
357
359
|
click.echo(f"Ready to install: {to_install}\n")
|
|
358
360
|
for library in to_install:
|
|
359
361
|
ctx.obj["backend"].install_module(
|
|
@@ -366,6 +368,26 @@ def install(
|
|
|
366
368
|
)
|
|
367
369
|
|
|
368
370
|
if stubs:
|
|
371
|
+
# Check we are in a virtual environment
|
|
372
|
+
if not is_virtual_env_active():
|
|
373
|
+
if is_global_install_ok is None:
|
|
374
|
+
click.secho(
|
|
375
|
+
(
|
|
376
|
+
"No virtual environment detected.\n"
|
|
377
|
+
"It is recommended to run circup inside a virtual environment "
|
|
378
|
+
"when installing stubs. Without a virtual environment, the stubs "
|
|
379
|
+
"will be installed to the global python."
|
|
380
|
+
),
|
|
381
|
+
fg="yellow",
|
|
382
|
+
)
|
|
383
|
+
is_global_install_ok = click.confirm(
|
|
384
|
+
click.style(
|
|
385
|
+
"Would you still like to install stubs (to the global python)?",
|
|
386
|
+
fg="yellow",
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
if not is_global_install_ok:
|
|
390
|
+
continue
|
|
369
391
|
library_stubs = "adafruit-circuitpython-{}".format(
|
|
370
392
|
library.replace("adafruit_", "")
|
|
371
393
|
)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 George Waters
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""
|
|
5
|
+
Class that acts similar to a dictionary, but defers the loading of expensive
|
|
6
|
+
data until that data is accessed.
|
|
7
|
+
"""
|
|
8
|
+
from typing import Any, Callable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LazyMetadata:
|
|
12
|
+
"""
|
|
13
|
+
Dictionary like class that stores module metadata. Expensive to load
|
|
14
|
+
metadata won't be loaded until it is accessed.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
deferred_load: Callable[[], dict[str, Any]],
|
|
20
|
+
initial_data: dict[str, Any] | None = None,
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
Initialize a LazyMetadata object by providing a callable and initial
|
|
24
|
+
data.
|
|
25
|
+
|
|
26
|
+
:param deferred_load: A callable that returns a dictionary of metadata.
|
|
27
|
+
This is not invoked until a key is accessed that is not available in
|
|
28
|
+
:py:attr:`initial_data`.
|
|
29
|
+
:param initial_data: A dictionary containing the initial metadata.
|
|
30
|
+
"""
|
|
31
|
+
self._deferred_load = deferred_load
|
|
32
|
+
self.initial_data = initial_data.copy() if initial_data is not None else {}
|
|
33
|
+
self._deferred_data: dict[str, Any] | None = None
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def deferred_data(self) -> dict[str, Any]:
|
|
37
|
+
"""
|
|
38
|
+
Lazy load the metadata from :py:attr:`_deferred_load`.
|
|
39
|
+
|
|
40
|
+
:return: The "expensive" metadata that was loaded from
|
|
41
|
+
:py:attr:`_deferred_load`.
|
|
42
|
+
"""
|
|
43
|
+
if self._deferred_data is None:
|
|
44
|
+
self._deferred_data = self._deferred_load()
|
|
45
|
+
return self._deferred_data
|
|
46
|
+
|
|
47
|
+
def __getitem__(self, key: str) -> Any:
|
|
48
|
+
"""
|
|
49
|
+
Get items via keyed index lookup, like a dictionary.
|
|
50
|
+
|
|
51
|
+
Keys are first looked for in :py:attr:`initial_data`, if the key isn't
|
|
52
|
+
found it is then looked for in :py:attr:`deferred_data`.
|
|
53
|
+
|
|
54
|
+
:param key: Key to a metadata value.
|
|
55
|
+
:return: Metadata value for the given key.
|
|
56
|
+
:raises KeyError: If the key cannot be found.
|
|
57
|
+
"""
|
|
58
|
+
if key in self.initial_data: # pylint: disable=no-else-return
|
|
59
|
+
return self.initial_data[key]
|
|
60
|
+
elif key in self.deferred_data:
|
|
61
|
+
return self.deferred_data[key]
|
|
62
|
+
raise KeyError(key)
|
|
63
|
+
|
|
64
|
+
def __setitem__(self, key: str, item: Any) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Sets the item under the given key.
|
|
67
|
+
|
|
68
|
+
The item is set in the :py:attr:`initial_data` dictionary.
|
|
69
|
+
|
|
70
|
+
:param key: Key to a metadata value.
|
|
71
|
+
:param item: Metadata value
|
|
72
|
+
"""
|
|
73
|
+
self.initial_data[key] = item
|
|
74
|
+
|
|
75
|
+
def __contains__(self, key: str):
|
|
76
|
+
"""
|
|
77
|
+
Whether or not a key is present.
|
|
78
|
+
|
|
79
|
+
This checks both :py:attr:`initial_data` and :py:attr:`deferred_data`
|
|
80
|
+
for the key. *Note* this will cause :py:attr:`deferred_data` to load
|
|
81
|
+
the deferred data if it is not already.
|
|
82
|
+
"""
|
|
83
|
+
return key in self.initial_data or key in self.deferred_data
|
|
84
|
+
|
|
85
|
+
def get(self, key: str, default: Any = None):
|
|
86
|
+
"""
|
|
87
|
+
Get items via keyed index lookup, like a dictionary.
|
|
88
|
+
|
|
89
|
+
Also like a dictionary, this method doesn't error if the key is not
|
|
90
|
+
found. :param default: is returned if the key is not found.
|
|
91
|
+
|
|
92
|
+
:param key: Key to a metadata value.
|
|
93
|
+
:param default: Default value to return when the key doesn't exist.
|
|
94
|
+
:return: Metadata value for the given key.
|
|
95
|
+
"""
|
|
96
|
+
if key in self:
|
|
97
|
+
return self[key]
|
|
98
|
+
return default
|
|
99
|
+
|
|
100
|
+
def __repr__(self) -> str:
|
|
101
|
+
"""
|
|
102
|
+
Helps with log files.
|
|
103
|
+
|
|
104
|
+
:return: A repr of a dictionary containing the metadata's values.
|
|
105
|
+
"""
|
|
106
|
+
return repr(
|
|
107
|
+
{
|
|
108
|
+
"initial_data": self.initial_data,
|
|
109
|
+
"deferred_data": self._deferred_data
|
|
110
|
+
if self._deferred_data is not None
|
|
111
|
+
else "<Not Loaded>",
|
|
112
|
+
}
|
|
113
|
+
)
|
|
@@ -14,6 +14,8 @@ import importlib.resources
|
|
|
14
14
|
import appdirs
|
|
15
15
|
import requests
|
|
16
16
|
|
|
17
|
+
from circup.lazy_metadata import LazyMetadata
|
|
18
|
+
|
|
17
19
|
#: Version identifier for a bad MPY file format
|
|
18
20
|
BAD_FILE_FORMAT = "Invalid"
|
|
19
21
|
|
|
@@ -51,7 +53,7 @@ NOT_MCU_LIBRARIES = [
|
|
|
51
53
|
BOARDLESS_COMMANDS = ["show", "bundle-add", "bundle-remove", "bundle-show"]
|
|
52
54
|
|
|
53
55
|
|
|
54
|
-
def _get_modules_file(path, logger):
|
|
56
|
+
def _get_modules_file(path, logger): # pylint: disable=too-many-locals
|
|
55
57
|
"""
|
|
56
58
|
Get a dictionary containing metadata about all the Python modules found in
|
|
57
59
|
the referenced file system path.
|
|
@@ -71,8 +73,10 @@ def _get_modules_file(path, logger):
|
|
|
71
73
|
]
|
|
72
74
|
single_file_mods = single_file_py_mods + single_file_mpy_mods
|
|
73
75
|
for sfm in [f for f in single_file_mods if not os.path.basename(f).startswith(".")]:
|
|
74
|
-
|
|
75
|
-
metadata
|
|
76
|
+
default_metadata = {"path": sfm, "mpy": sfm.endswith(".mpy")}
|
|
77
|
+
metadata = LazyMetadata(
|
|
78
|
+
lambda sfm=sfm: extract_metadata(sfm, logger), default_metadata
|
|
79
|
+
)
|
|
76
80
|
result[os.path.basename(sfm).replace(".py", "").replace(".mpy", "")] = metadata
|
|
77
81
|
for package_path in package_dir_mods:
|
|
78
82
|
name = os.path.basename(os.path.dirname(package_path))
|
|
@@ -81,19 +85,27 @@ def _get_modules_file(path, logger):
|
|
|
81
85
|
all_files = py_files + mpy_files
|
|
82
86
|
# put __init__ first if any, assumed to have the version number
|
|
83
87
|
all_files.sort()
|
|
88
|
+
|
|
89
|
+
def get_metadata(all_files=all_files): # capture all_files
|
|
90
|
+
selected_metadata = {}
|
|
91
|
+
# explore all the submodules to detect bad ones
|
|
92
|
+
for source in [
|
|
93
|
+
f for f in all_files if not os.path.basename(f).startswith(".")
|
|
94
|
+
]:
|
|
95
|
+
metadata = extract_metadata(source, logger)
|
|
96
|
+
if "__version__" in metadata:
|
|
97
|
+
# don't replace metadata if already found
|
|
98
|
+
if "__version__" not in selected_metadata:
|
|
99
|
+
selected_metadata = metadata
|
|
100
|
+
# break now if any of the submodules has a bad format
|
|
101
|
+
if metadata["__version__"] == BAD_FILE_FORMAT:
|
|
102
|
+
break
|
|
103
|
+
return selected_metadata
|
|
104
|
+
|
|
84
105
|
# default value
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
metadata = extract_metadata(source, logger)
|
|
89
|
-
if "__version__" in 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
|
|
94
|
-
# break now if any of the submodules has a bad format
|
|
95
|
-
if metadata["__version__"] == BAD_FILE_FORMAT:
|
|
96
|
-
break
|
|
106
|
+
default_metadata = {"path": package_path, "mpy": bool(mpy_files)}
|
|
107
|
+
metadata = LazyMetadata(get_metadata, default_metadata)
|
|
108
|
+
result[name] = metadata
|
|
97
109
|
return result
|
|
98
110
|
|
|
99
111
|
|
|
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
|
|
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
|
|
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
|