From e222d17d2c1ff8e1e085bc1aca04d776f6fd5dc3 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 30 Apr 2024 13:57:09 -0700 Subject: [PATCH] Ensure that conflicting packages are not installed Allow the user to override version pins globally via Config.frontend_packages --- reflex/app.py | 16 ++++++++++++---- reflex/utils/imports.py | 18 ++++++++++++++++-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index dff45151e..67b36b3e7 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -79,7 +79,7 @@ from reflex.state import ( ) from reflex.utils import console, exceptions, format, prerequisites, types from reflex.utils.exec import is_testing_env, should_skip_compile -from reflex.utils.imports import ImportList +from reflex.utils.imports import ImportList, split_library_name_version # Define custom types. ComponentCallable = Callable[[], Component] @@ -627,7 +627,8 @@ class App(Base): Example: >>> get_frontend_packages({"react": "16.14.0", "react-dom": "16.14.0"}) """ - page_imports = [i.package for i in imports if i.install and i.package] + page_imports = ImportList(i for i in imports if i.install and i.package) + inferred_libraries = [i.library for i in page_imports] frontend_packages = get_config().frontend_packages _frontend_packages = [] for package in frontend_packages: @@ -636,14 +637,21 @@ class App(Base): f"Tailwind packages are inferred from 'plugins', remove `{package}` from `frontend_packages`" ) continue - if package in page_imports: + lib, version = split_library_name_version(package) + if ( + lib in inferred_libraries + and version is None + or version == page_imports[inferred_libraries.index(lib)].version + ): console.warn( f"React packages and their dependencies are inferred from Component.library and Component.lib_dependencies, remove `{package}` from `frontend_packages`" ) continue _frontend_packages.append(package) page_imports.extend(_frontend_packages) - prerequisites.install_frontend_packages(set(page_imports), get_config()) + prerequisites.install_frontend_packages( + set(page_imports.collapse()), get_config() + ) def _app_root(self, app_wrappers: dict[tuple[int, str], Component]) -> Component: for component in tuple(app_wrappers.values()): diff --git a/reflex/utils/imports.py b/reflex/utils/imports.py index 5c8062772..d894c7c2c 100644 --- a/reflex/utils/imports.py +++ b/reflex/utils/imports.py @@ -258,7 +258,10 @@ class ImportList(List[ImportVar]): """When collapsing an import list, prefer packages with version specifiers. Returns: - The collapsed import dict ({library_name: [import_var1, ...]}). + The collapsed import dict ({package_spec: [import_var1, ...]}). + + Raises: + ValueError: If two imports have conflicting version specifiers. """ collapsed: dict[str, dict[ImportVar, ImportVar]] = {} for imp in self: @@ -271,7 +274,18 @@ class ImportList(List[ImportVar]): collapsed[lib][imp] = existing_imp.collapse(imp) else: collapsed[lib][imp] = imp - return {lib: list(set(imps)) for lib, imps in collapsed.items()} + + # Check that all tags in the given library have the same version. + deduped: ImportDict = {} + for lib, imps in collapsed.items(): + packages = {imp.package for imp in imps if imp.version is not None} + if len(packages) > 1: + raise ValueError( + f"Imports from {lib} have conflicting version specifiers: " + f"{packages} {imps}" + ) + deduped[list(packages)[0] or ""] = list(imps.values()) + return deduped ImportDict = Dict[str, List[ImportVar]]