Skip frontend packages install if previously done (#2400)

This commit is contained in:
jackie-pc 2024-01-16 17:52:28 -08:00 committed by GitHub
parent 1aca1b677f
commit 2c270585ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 94 additions and 6 deletions

View File

@ -595,7 +595,7 @@ class App(Base):
continue continue
_frontend_packages.append(package) _frontend_packages.append(package)
page_imports.update(_frontend_packages) page_imports.update(_frontend_packages)
prerequisites.install_frontend_packages(page_imports) prerequisites.install_frontend_packages(page_imports, get_config())
def _app_root(self, app_wrappers: dict[tuple[int, str], Component]) -> Component: def _app_root(self, app_wrappers: dict[tuple[int, str], Component]) -> Component:
for component in tuple(app_wrappers.values()): for component in tuple(app_wrappers.values()):

View File

@ -16,6 +16,7 @@ import zipfile
from fileinput import FileInput from fileinput import FileInput
from pathlib import Path from pathlib import Path
from types import ModuleType from types import ModuleType
from typing import Callable
import httpx import httpx
import pkg_resources import pkg_resources
@ -26,7 +27,7 @@ from redis.asyncio import Redis
from reflex import constants, model from reflex import constants, model
from reflex.compiler import templates from reflex.compiler import templates
from reflex.config import get_config from reflex.config import Config, get_config
from reflex.utils import console, path_ops, processes from reflex.utils import console, path_ops, processes
@ -619,14 +620,64 @@ def install_bun():
) )
def install_frontend_packages(packages: set[str]): def _write_cached_procedure_file(payload: str, cache_file: str):
with open(cache_file, "w") as f:
f.write(payload)
def _read_cached_procedure_file(cache_file: str) -> str | None:
if os.path.exists(cache_file):
with open(cache_file, "r") as f:
return f.read()
return None
def _clear_cached_procedure_file(cache_file: str):
if os.path.exists(cache_file):
os.remove(cache_file)
def cached_procedure(cache_file: str, payload_fn: Callable[..., str]):
"""Decorator to cache the runs of a procedure on disk. Procedures should not have
a return value.
Args:
cache_file: The file to store the cache payload in.
payload_fn: Function that computes cache payload from function args
Returns:
The decorated function.
"""
def _inner_decorator(func):
def _inner(*args, **kwargs):
payload = _read_cached_procedure_file(cache_file)
new_payload = payload_fn(*args, **kwargs)
if payload != new_payload:
_clear_cached_procedure_file(cache_file)
func(*args, **kwargs)
_write_cached_procedure_file(new_payload, cache_file)
return _inner
return _inner_decorator
@cached_procedure(
cache_file=os.path.join(
constants.Dirs.WEB, "reflex.install_frontend_packages.cached"
),
payload_fn=lambda p, c: f"{repr(sorted(list(p)))},{c.json()}",
)
def install_frontend_packages(packages: set[str], config: Config):
"""Installs the base and custom frontend packages. """Installs the base and custom frontend packages.
Args: Args:
packages: A list of package names to be installed. packages: A list of package names to be installed.
config: The config object.
Example: Example:
>>> install_frontend_packages(["react", "react-dom"]) >>> install_frontend_packages(["react", "react-dom"], get_config())
""" """
# Install the base packages. # Install the base packages.
process = processes.new_process( process = processes.new_process(
@ -637,7 +688,6 @@ def install_frontend_packages(packages: set[str]):
processes.show_status("Installing base frontend packages", process) processes.show_status("Installing base frontend packages", process)
config = get_config()
if config.tailwind is not None: if config.tailwind is not None:
# install tailwind and tailwind plugins as dev dependencies. # install tailwind and tailwind plugins as dev dependencies.
process = processes.new_process( process = processes.new_process(

View File

@ -1,10 +1,15 @@
import tempfile
from unittest.mock import Mock, mock_open from unittest.mock import Mock, mock_open
import pytest import pytest
from reflex import constants from reflex import constants
from reflex.config import Config from reflex.config import Config
from reflex.utils.prerequisites import _update_next_config, initialize_requirements_txt from reflex.utils.prerequisites import (
_update_next_config,
cached_procedure,
initialize_requirements_txt,
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -139,3 +144,36 @@ def test_requirements_txt_other_encoding(mocker):
open_mock().write.call_args[0][0] open_mock().write.call_args[0][0]
== f"\n{constants.RequirementsTxt.DEFAULTS_STUB}{constants.Reflex.VERSION}\n" == f"\n{constants.RequirementsTxt.DEFAULTS_STUB}{constants.Reflex.VERSION}\n"
) )
def test_cached_procedure():
call_count = 0
@cached_procedure(tempfile.mktemp(), payload_fn=lambda: "constant")
def _function_with_no_args():
nonlocal call_count
call_count += 1
_function_with_no_args()
assert call_count == 1
_function_with_no_args()
assert call_count == 1
call_count = 0
@cached_procedure(
tempfile.mktemp(),
payload_fn=lambda *args, **kwargs: f"{repr(args), repr(kwargs)}",
)
def _function_with_some_args(*args, **kwargs):
nonlocal call_count
call_count += 1
_function_with_some_args(1, y=2)
assert call_count == 1
_function_with_some_args(1, y=2)
assert call_count == 1
_function_with_some_args(100, y=300)
assert call_count == 2
_function_with_some_args(100, y=300)
assert call_count == 2