From 6b4a5a5180a002096a0e94ee15e7a428f9575a48 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 13:12:31 -0500 Subject: [PATCH 01/14] Fix logic --- shinylive/_deps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shinylive/_deps.py b/shinylive/_deps.py index 80f5c6f..44d94a9 100644 --- a/shinylive/_deps.py +++ b/shinylive/_deps.py @@ -468,7 +468,7 @@ def _find_requirements_app_contents(app_contents: list[FileContentJson]) -> set[ """ packages: set[str] = set() for file_content in app_contents: - if not file_content["name"] != "requirements.txt": + if file_content["name"] != "requirements.txt": continue packages = packages.union( From 13b6a161466f108de5e859263edaef9f9a14ae01 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 13:13:15 -0500 Subject: [PATCH 02/14] Fix detection of packages where key and name differ --- shinylive/_deps.py | 97 +++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 35 deletions(-) diff --git a/shinylive/_deps.py b/shinylive/_deps.py index 44d94a9..802a695 100644 --- a/shinylive/_deps.py +++ b/shinylive/_deps.py @@ -36,7 +36,7 @@ } # Packages that should always be included in a Shinylive deployment. -BASE_PYODIDE_PACKAGES = {"distutils", "micropip", "ssl"} +BASE_PYODIDE_PACKAGE_NAMES = {"distutils", "micropip", "ssl"} AssetType = Literal["base", "python", "r"] @@ -56,6 +56,11 @@ class PyodidePackageInfo(TypedDict): # The package information structure used by Pyodide's pyodide-lock.json. +# Note that the key in `packages` may be something like "jsonschema-specifications", +# but the actual name of the package may be different, like "jsonschema_specifications". +# (The "name" entry in the PyodidePackageInfo object is the actual package name.) +# And also further note that the module names in the "imports" list are not necessarily +# the same as either: the "opencv-python" package has a module name "cv2". class PyodideLockFile(TypedDict): info: dict[str, str] packages: dict[str, PyodidePackageInfo] @@ -93,7 +98,8 @@ def _dep_names_to_pyodide_pkg_infos( ) -> list[PyodidePackageInfo]: pyodide_lock = _pyodide_lock_data() pkg_infos: list[PyodidePackageInfo] = [ - copy.deepcopy(pyodide_lock["packages"][dep_name]) for dep_name in dep_names + copy.deepcopy(pyodide_lock["packages"][dep_name_to_dep_key(dep_name)]) + for dep_name in dep_names ] return pkg_infos @@ -378,7 +384,7 @@ def base_package_deps() -> list[PyodidePackageInfo]: Return list of python packages that should be included in all python Shinylive deployments. The returned data structure is a list of PyodidePackageInfo objects. """ - dep_names = _find_recursive_deps(BASE_PYODIDE_PACKAGES) + dep_names = _find_recursive_deps(BASE_PYODIDE_PACKAGE_NAMES) pkg_infos = _dep_names_to_pyodide_pkg_infos(dep_names) return pkg_infos @@ -388,7 +394,7 @@ def base_package_deps() -> list[PyodidePackageInfo]: # Internal functions # ============================================================================= def _find_recursive_deps( - pkgs: Iterable[str], + dep_names: Iterable[str], verbose_print: Callable[..., None] = lambda *args: None, ) -> list[str]: """ @@ -397,45 +403,66 @@ def _find_recursive_deps( packages passed in. """ pyodide_lock = _pyodide_lock_data() - deps = list(pkgs) + + # The keys in pyodide_lock are not the same as the package names. For example, the + # key "jsonschema-specifications" points to an object where the "name" entry is + # "jsonschema_specifications". The dependencies are listed with names, not keys. + + dep_names = list(dep_names) i = 0 - while i < len(deps): - dep = deps[i] - if dep not in pyodide_lock["packages"]: - # TODO: Need to distinguish between built-in packages and external ones in - # requirements.txt. - verbose_print( - f" {dep} not in pyodide-lock.json. Assuming it is in base Pyodide or in requirements.txt." - ) - deps.remove(dep) + while i < len(dep_names): + dep_name = dep_names[i] + dep_key = dep_name_to_dep_key(dep_name) + + if dep_key not in pyodide_lock["packages"]: + if dep_name not in BASE_PYODIDE_PACKAGE_NAMES: + # TODO: Need to distinguish between built-in packages and external ones in + # requirements.txt. + verbose_print( + f" {dep_name} not in pyodide-lock.json. Assuming it is in base Pyodide or in requirements.txt." + ) + dep_names.remove(dep_name) continue - dep_deps = set(pyodide_lock["packages"][dep]["depends"]) - new_deps = dep_deps.difference(deps) - deps.extend(new_deps) + dep_depnames = set(pyodide_lock["packages"][dep_key]["depends"]) + new_depnames = dep_depnames.difference(dep_names) + dep_names.extend(new_depnames) i += 1 - return deps + return dep_names -def _dep_name_to_dep_file(dep_name: str) -> str: +def dep_name_to_dep_key(name: str) -> str: """ - Given the name of a dependency, like "pandas", return the name of the .whl file, - like "pandas-1.4.2-cp310-cp310-emscripten_3_1_14_wasm32.whl". + Convert a package name to a key that can be used to look up the package in + pyodide-lock.json. + + The keys in pyodide-lock.json are not the same as the package names. For example, + the key "jsonschema-specifications" points to an object where the "name" entry is + "jsonschema_specifications". """ - pyodide_lock = _pyodide_lock_data() - return pyodide_lock["packages"][dep_name]["file_name"] + # Special case for base pyodide packages + if name in BASE_PYODIDE_PACKAGE_NAMES: + return name + + return _dep_name_to_dep_key_mappings()[name] -def _dep_names_to_dep_files(dep_names: list[str]) -> list[str]: +@functools.lru_cache +def _dep_name_to_dep_key_mappings() -> dict[str, str]: """ - Given a list of dependency names, like ["pandas"], return a list with the names of - corresponding .whl files (from data in pyodide-lock.json), like - ["pandas-1.4.2-cp310-cp310-emscripten_3_1_14_wasm32.whl"]. + Return a dictionary that maps package names to keys. This is needed because + sometimes the package name and package key are different. For example, the package + name is "jsonschema_specifications", but the package name is + "jsonschema-specifications". """ + name_to_key: dict[str, str] = {} + pyodide_lock = _pyodide_lock_data() - dep_files = [pyodide_lock["packages"][x]["file_name"] for x in dep_names] - return dep_files + for key, pkg_info in pyodide_lock["packages"].items(): + name_to_key[pkg_info["name"]] = key + + return name_to_key def _find_import_app_contents(app_contents: list[FileContentJson]) -> set[str]: @@ -452,7 +479,7 @@ def _find_import_app_contents(app_contents: list[FileContentJson]) -> set[str]: # Note that at this point, the imports are module names, like "cv2", but these can # sometimes differ from the package names, like "opencv-python". We need to map from # module names to package names. - packages = [module_to_package(x) for x in imports] + packages = [module_to_package_key(x) for x in imports] packages = [x for x in packages if x is not None] return set(packages) @@ -478,12 +505,12 @@ def _find_requirements_app_contents(app_contents: list[FileContentJson]) -> set[ return packages -def module_to_package(module: str) -> str | None: +def module_to_package_key(module: str) -> str | None: """ Given a module name, like "cv2", return the corresponding package name, like "opencv-python". If not found, return None. """ - module_to_package = _module_to_package_mappings() + module_to_package = _module_to_package_key_mappings() if module in module_to_package: return module_to_package[module] else: @@ -491,7 +518,7 @@ def module_to_package(module: str) -> str | None: @functools.lru_cache -def _module_to_package_mappings() -> dict[str, str]: +def _module_to_package_key_mappings() -> dict[str, str]: """ Return a dictionary that maps module names to package names. This is needed because sometimes the module name and package name are different. For example, the module @@ -499,10 +526,10 @@ def _module_to_package_mappings() -> dict[str, str]: """ pyodide_lock = _pyodide_lock_data() module_to_package: dict[str, str] = {} - for pkg_name, pkg_info in pyodide_lock["packages"].items(): + for pkg_key, pkg_info in pyodide_lock["packages"].items(): modules = pkg_info["imports"] for module in modules: - module_to_package[module] = pkg_name + module_to_package[module] = pkg_key return module_to_package From 44eb0c9f7e9ac0a223a35ef209fc645a71d47f9d Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 14:47:01 -0500 Subject: [PATCH 03/14] Be case insensitive for package names --- shinylive/_deps.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/shinylive/_deps.py b/shinylive/_deps.py index 802a695..111cb7f 100644 --- a/shinylive/_deps.py +++ b/shinylive/_deps.py @@ -440,12 +440,15 @@ def dep_name_to_dep_key(name: str) -> str: The keys in pyodide-lock.json are not the same as the package names. For example, the key "jsonschema-specifications" points to an object where the "name" entry is "jsonschema_specifications". + + Note that the names are lowercased because the package names should be treated as + case-insensitive. https://github.com/pyodide/pyodide/issues/1614 """ # Special case for base pyodide packages if name in BASE_PYODIDE_PACKAGE_NAMES: return name - return _dep_name_to_dep_key_mappings()[name] + return _dep_name_to_dep_key_mappings()[name.lower()] @functools.lru_cache @@ -455,12 +458,16 @@ def _dep_name_to_dep_key_mappings() -> dict[str, str]: sometimes the package name and package key are different. For example, the package name is "jsonschema_specifications", but the package name is "jsonschema-specifications". + + Note that the names are lowercased because the package names should be treated as + case-insensitive. https://github.com/pyodide/pyodide/issues/1614 """ name_to_key: dict[str, str] = {} pyodide_lock = _pyodide_lock_data() for key, pkg_info in pyodide_lock["packages"].items(): - name_to_key[pkg_info["name"]] = key + name = pkg_info["name"].lower() + name_to_key[name] = key return name_to_key From 4021e47f65c415beca7d71fe821828178a13e4d2 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 14:47:09 -0500 Subject: [PATCH 04/14] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9cbff8..5679cee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [UNRELEASED] + +* Fixed a problem with dependency detection when the package name differed from the key in package-lock.json (#36). + ## [0.5.0] - 2025-07-18 * Updated to Shinylive web assets 0.5.0. From 3d93bbba8ba473589e454d6e3da70e08c8ab35e1 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 14:53:35 -0500 Subject: [PATCH 05/14] Fix escaping --- shinylive/_deps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shinylive/_deps.py b/shinylive/_deps.py index 111cb7f..d8695fc 100644 --- a/shinylive/_deps.py +++ b/shinylive/_deps.py @@ -631,7 +631,7 @@ def _find_packages_in_requirements(req_txt: str) -> list[str]: else: # If we got here, it's a package specification. # Remove any trailing version info: "my-package (>= 1.0.0)" -> "my-package" - pkg_name = re.sub(r"([a-zA-Z0-9._-]+)(.*)", r"\\1", line).strip() + pkg_name = re.sub(r"([a-zA-Z0-9._-]+)(.*)", r"\1", line).strip() reqs.append(pkg_name) return reqs From 13f2e32ff26cf353541e08301cc010577beb9bc1 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 15:10:27 -0500 Subject: [PATCH 06/14] Allow - and _ in requirements.txt --- shinylive/_deps.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shinylive/_deps.py b/shinylive/_deps.py index d8695fc..2957554 100644 --- a/shinylive/_deps.py +++ b/shinylive/_deps.py @@ -632,6 +632,9 @@ def _find_packages_in_requirements(req_txt: str) -> list[str]: # If we got here, it's a package specification. # Remove any trailing version info: "my-package (>= 1.0.0)" -> "my-package" pkg_name = re.sub(r"([a-zA-Z0-9._-]+)(.*)", r"\1", line).strip() + # Replace underscores with hyphens: "typing_extensions" -> "typing-extensions" + pkg_name = pkg_name.replace("_", "-") + reqs.append(pkg_name) return reqs From 93df443b118e78463922e084ec2258a8a1d04b6f Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 15:10:49 -0500 Subject: [PATCH 07/14] Don't error if package not found in pyodide_lock --- shinylive/_deps.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/shinylive/_deps.py b/shinylive/_deps.py index 2957554..f7c53f0 100644 --- a/shinylive/_deps.py +++ b/shinylive/_deps.py @@ -97,10 +97,15 @@ def _dep_names_to_pyodide_pkg_infos( dep_names: Iterable[str], ) -> list[PyodidePackageInfo]: pyodide_lock = _pyodide_lock_data() - pkg_infos: list[PyodidePackageInfo] = [ - copy.deepcopy(pyodide_lock["packages"][dep_name_to_dep_key(dep_name)]) - for dep_name in dep_names - ] + pkg_infos: list[PyodidePackageInfo] = [] + + for dep_name in dep_names: + dep_key = dep_name_to_dep_key(dep_name) + if dep_key is None: + continue + pkg_info = copy.deepcopy(pyodide_lock["packages"][dep_key]) + pkg_infos.append(pkg_info) + return pkg_infos @@ -412,7 +417,7 @@ def _find_recursive_deps( i = 0 while i < len(dep_names): dep_name = dep_names[i] - dep_key = dep_name_to_dep_key(dep_name) + dep_key: str | None = dep_name_to_dep_key(dep_name) if dep_key not in pyodide_lock["packages"]: if dep_name not in BASE_PYODIDE_PACKAGE_NAMES: @@ -432,7 +437,7 @@ def _find_recursive_deps( return dep_names -def dep_name_to_dep_key(name: str) -> str: +def dep_name_to_dep_key(name: str) -> str | None: """ Convert a package name to a key that can be used to look up the package in pyodide-lock.json. @@ -448,7 +453,11 @@ def dep_name_to_dep_key(name: str) -> str: if name in BASE_PYODIDE_PACKAGE_NAMES: return name - return _dep_name_to_dep_key_mappings()[name.lower()] + name = name.lower() + if name not in _dep_name_to_dep_key_mappings(): + return None + + return _dep_name_to_dep_key_mappings()[name] @functools.lru_cache From d2f5c0a0e3e49cfa55efbc391e94e1b590e8ccc3 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 15:13:04 -0500 Subject: [PATCH 08/14] Bump version to 0.5.0.9000 --- shinylive/_version/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shinylive/_version/__init__.py b/shinylive/_version/__init__.py index 9c75e65..94aacb9 100644 --- a/shinylive/_version/__init__.py +++ b/shinylive/_version/__init__.py @@ -1,5 +1,5 @@ # The version of this Python package. -SHINYLIVE_PACKAGE_VERSION = "0.5.0" +SHINYLIVE_PACKAGE_VERSION = "0.5.0.9000" # This is the version of the Shinylive assets to use. SHINYLIVE_ASSETS_VERSION = "0.5.0" From ee8a46eb90e1ce427ba40a6df9cc7e33f5291941 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 15:46:19 -0500 Subject: [PATCH 09/14] Add tests for dependency detection --- tests/test_assets.py | 2 +- tests/test_deps.py | 71 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/test_deps.py diff --git a/tests/test_assets.py b/tests/test_assets.py index e410012..65f8b15 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -1,4 +1,4 @@ -"""Tests for Shinlyive assets.""" +"""Tests for Shinylive assets.""" import os diff --git a/tests/test_deps.py b/tests/test_deps.py new file mode 100644 index 0000000..7adfa04 --- /dev/null +++ b/tests/test_deps.py @@ -0,0 +1,71 @@ +"""Tests for Shinylive dependency detection.""" + + +def test_requirements_txt(): + from shinylive._deps import _find_packages_in_requirements + + requirements_txt = """ + typing_extensions + jsonschema-specifications (<1.0) + # comment + """ + + # This should convert '_' to '-', and remove the version constraints. + assert _find_packages_in_requirements(requirements_txt) == [ + "typing-extensions", + "jsonschema-specifications", + ] + + # Should preserve case here (in other steps it will be lowercased). + assert _find_packages_in_requirements("Jinja2") == ["Jinja2"] + assert _find_packages_in_requirements("jinja2") == ["jinja2"] + + +def test_module_to_package_key(): + from shinylive._deps import module_to_package_key + + assert module_to_package_key("cv2") == "opencv-python" + assert module_to_package_key("black") == "black" + assert module_to_package_key("foobar") == None + + +def test_dep_name_to_dep_key(): + from shinylive._deps import dep_name_to_dep_key + + assert dep_name_to_dep_key("black") == "black" + assert dep_name_to_dep_key("typing-extensions") == "typing-extensions" + assert ( + dep_name_to_dep_key("jsonschema_specifications-tests") + == "jsonschema-specifications-tests" + ) + + # Should not convert `_` to `-` + assert dep_name_to_dep_key("typing_extensions") == None + + # Should be case insensitive to input. + assert dep_name_to_dep_key("Jinja2") == "jinja2" + assert dep_name_to_dep_key("JiNJa2") == "jinja2" + + assert dep_name_to_dep_key("cv2") == None + + +def test_find_recursive_deps(): + from shinylive._deps import _find_recursive_deps + + # It is possible that these dependencies will change in future versions of Pyodide, + # but the reason we're testing jsonschema specifically is because it includes + # jsonschema_specifications, which is the package name (and not the key). + assert sorted(_find_recursive_deps(["jsonschema"])) == [ + "attrs", + "jsonschema", + "jsonschema_specifications", + "pyrsistent", + "referencing", + "rpds-py", + "six", + ] + + assert sorted(_find_recursive_deps(["opencv-python"])) == [ + "numpy", + "opencv-python", + ] From 41aac9e54006241b505ce777a38ff571150b6288 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 15:52:31 -0500 Subject: [PATCH 10/14] Skip some tests in CI --- tests/test_deps.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_deps.py b/tests/test_deps.py index 7adfa04..18a79ee 100644 --- a/tests/test_deps.py +++ b/tests/test_deps.py @@ -1,5 +1,9 @@ """Tests for Shinylive dependency detection.""" +import os + +import pytest + def test_requirements_txt(): from shinylive._deps import _find_packages_in_requirements @@ -21,6 +25,16 @@ def test_requirements_txt(): assert _find_packages_in_requirements("jinja2") == ["jinja2"] +# Don't run remaining tests in CI, unless we're triggered by a release event. This is +# because they require the assets to be installed. In the future, it would make sense to +# run this test when we're on an rc branch. +if os.environ.get("CI") == "true" and os.environ.get("GITHUB_EVENT_NAME") != "release": + pytest.skip( + reason="Don't run this test in CI, unless we're on a release branch.", + allow_module_level=True, + ) + + def test_module_to_package_key(): from shinylive._deps import module_to_package_key From 3cbde49e5808400db852b2b7ddb3d6826a1e32b8 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 15:54:09 -0500 Subject: [PATCH 11/14] Use 'is None' instead of '== None' --- tests/test_deps.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_deps.py b/tests/test_deps.py index 18a79ee..8725271 100644 --- a/tests/test_deps.py +++ b/tests/test_deps.py @@ -40,7 +40,7 @@ def test_module_to_package_key(): assert module_to_package_key("cv2") == "opencv-python" assert module_to_package_key("black") == "black" - assert module_to_package_key("foobar") == None + assert module_to_package_key("foobar") is None def test_dep_name_to_dep_key(): @@ -54,13 +54,13 @@ def test_dep_name_to_dep_key(): ) # Should not convert `_` to `-` - assert dep_name_to_dep_key("typing_extensions") == None + assert dep_name_to_dep_key("typing_extensions") is None # Should be case insensitive to input. assert dep_name_to_dep_key("Jinja2") == "jinja2" assert dep_name_to_dep_key("JiNJa2") == "jinja2" - assert dep_name_to_dep_key("cv2") == None + assert dep_name_to_dep_key("cv2") is None def test_find_recursive_deps(): From 6d2012530f63e964c8e7a6a5600bd640e2f45018 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 15:55:05 -0500 Subject: [PATCH 12/14] Clearer comment block --- tests/test_deps.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_deps.py b/tests/test_deps.py index 8725271..d7bf80a 100644 --- a/tests/test_deps.py +++ b/tests/test_deps.py @@ -25,9 +25,11 @@ def test_requirements_txt(): assert _find_packages_in_requirements("jinja2") == ["jinja2"] +# ====================================================================================== # Don't run remaining tests in CI, unless we're triggered by a release event. This is # because they require the assets to be installed. In the future, it would make sense to # run this test when we're on an rc branch. +# ====================================================================================== if os.environ.get("CI") == "true" and os.environ.get("GITHUB_EVENT_NAME") != "release": pytest.skip( reason="Don't run this test in CI, unless we're on a release branch.", From 7c034fd73d718447e3d1eef3d2b5f8a6ba99feb5 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 15:56:48 -0500 Subject: [PATCH 13/14] Fix typo --- shinylive/_deps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shinylive/_deps.py b/shinylive/_deps.py index f7c53f0..1fe91e5 100644 --- a/shinylive/_deps.py +++ b/shinylive/_deps.py @@ -465,7 +465,7 @@ def _dep_name_to_dep_key_mappings() -> dict[str, str]: """ Return a dictionary that maps package names to keys. This is needed because sometimes the package name and package key are different. For example, the package - name is "jsonschema_specifications", but the package name is + name is "jsonschema_specifications", but the package key is "jsonschema-specifications". Note that the names are lowercased because the package names should be treated as From c3705072d0c0443d2198adade626daf2e6c12a03 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 24 Jul 2024 16:15:48 -0500 Subject: [PATCH 14/14] Add more tests --- tests/test_deps.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_deps.py b/tests/test_deps.py index d7bf80a..b88f48f 100644 --- a/tests/test_deps.py +++ b/tests/test_deps.py @@ -42,6 +42,11 @@ def test_module_to_package_key(): assert module_to_package_key("cv2") == "opencv-python" assert module_to_package_key("black") == "black" + assert module_to_package_key("jinja2") == "jinja2" + + # Should be case sensitive for module names. + assert module_to_package_key("Jinja2") is None + assert module_to_package_key("foobar") is None @@ -64,6 +69,10 @@ def test_dep_name_to_dep_key(): assert dep_name_to_dep_key("cv2") is None + # Special case for a base pyodide package. It is not in pyodide_lock.json but should + # be included in the list of dependencies. + assert dep_name_to_dep_key("distutils") == "distutils" + def test_find_recursive_deps(): from shinylive._deps import _find_recursive_deps