improve hot reload handling (#4795)

This commit is contained in:
Khaleel Al-Adhami 2025-02-11 11:39:28 -08:00 committed by GitHub
parent 90be664981
commit a194c90d6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 74 additions and 10 deletions

View File

@ -23,6 +23,7 @@ from typing import (
Set, Set,
TypeVar, TypeVar,
get_args, get_args,
get_origin,
) )
from typing_extensions import Annotated, get_type_hints from typing_extensions import Annotated, get_type_hints
@ -304,6 +305,15 @@ def interpret_env_var_value(
return interpret_path_env(value, field_name) return interpret_path_env(value, field_name)
elif field_type is ExistingPath: elif field_type is ExistingPath:
return interpret_existing_path_env(value, field_name) return interpret_existing_path_env(value, field_name)
elif get_origin(field_type) is list:
return [
interpret_env_var_value(
v,
get_args(field_type)[0],
f"{field_name}[{i}]",
)
for i, v in enumerate(value.split(":"))
]
elif inspect.isclass(field_type) and issubclass(field_type, enum.Enum): elif inspect.isclass(field_type) and issubclass(field_type, enum.Enum):
return interpret_enum_env(value, field_type, field_name) return interpret_enum_env(value, field_type, field_name)
@ -387,7 +397,11 @@ class EnvVar(Generic[T]):
else: else:
if isinstance(value, enum.Enum): if isinstance(value, enum.Enum):
value = value.value value = value.value
os.environ[self.name] = str(value) if isinstance(value, list):
str_value = ":".join(str(v) for v in value)
else:
str_value = str(value)
os.environ[self.name] = str_value
class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration] class env_var: # noqa: N801 # pyright: ignore [reportRedeclaration]
@ -571,6 +585,12 @@ class EnvironmentVariables:
# Whether to use the turbopack bundler. # Whether to use the turbopack bundler.
REFLEX_USE_TURBOPACK: EnvVar[bool] = env_var(True) REFLEX_USE_TURBOPACK: EnvVar[bool] = env_var(True)
# Additional paths to include in the hot reload. Separated by a colon.
REFLEX_HOT_RELOAD_INCLUDE_PATHS: EnvVar[List[Path]] = env_var([])
# Paths to exclude from the hot reload. Takes precedence over include paths. Separated by a colon.
REFLEX_HOT_RELOAD_EXCLUDE_PATHS: EnvVar[List[Path]] = env_var([])
environment = EnvironmentVariables() environment = EnvironmentVariables()

View File

@ -10,6 +10,7 @@ import re
import subprocess import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Sequence
from urllib.parse import urljoin from urllib.parse import urljoin
import psutil import psutil
@ -242,14 +243,14 @@ def run_backend(
run_uvicorn_backend(host, port, loglevel) run_uvicorn_backend(host, port, loglevel)
def get_reload_dirs() -> list[Path]: def get_reload_paths() -> Sequence[Path]:
"""Get the reload directories for the backend. """Get the reload paths for the backend.
Returns: Returns:
The reload directories for the backend. The reload paths for the backend.
""" """
config = get_config() config = get_config()
reload_dirs = [Path(config.app_name)] reload_paths = [Path(config.app_name).parent]
if config.app_module is not None and config.app_module.__file__: if config.app_module is not None and config.app_module.__file__:
module_path = Path(config.app_module.__file__).resolve().parent module_path = Path(config.app_module.__file__).resolve().parent
@ -263,8 +264,43 @@ def get_reload_dirs() -> list[Path]:
else: else:
break break
reload_dirs = [module_path] reload_paths = [module_path]
return reload_dirs
include_dirs = tuple(
map(Path.absolute, environment.REFLEX_HOT_RELOAD_INCLUDE_PATHS.get())
)
exclude_dirs = tuple(
map(Path.absolute, environment.REFLEX_HOT_RELOAD_EXCLUDE_PATHS.get())
)
def is_excluded_by_default(path: Path) -> bool:
if path.is_dir():
if path.name.startswith("."):
# exclude hidden directories
return True
if path.name.startswith("__"):
# ignore things like __pycache__
return True
return path.name not in (".gitignore", "uploaded_files")
reload_paths = (
tuple(
path.absolute()
for dir in reload_paths
for path in dir.iterdir()
if not is_excluded_by_default(path)
)
+ include_dirs
)
if exclude_dirs:
reload_paths = tuple(
path
for path in reload_paths
if all(not path.samefile(exclude) for exclude in exclude_dirs)
)
return reload_paths
def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel): def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
@ -283,7 +319,7 @@ def run_uvicorn_backend(host: str, port: int, loglevel: LogLevel):
port=port, port=port,
log_level=loglevel.value, log_level=loglevel.value,
reload=True, reload=True,
reload_dirs=list(map(str, get_reload_dirs())), reload_dirs=list(map(str, get_reload_paths())),
) )
@ -310,8 +346,7 @@ def run_granian_backend(host: str, port: int, loglevel: LogLevel):
interface=Interfaces.ASGI, interface=Interfaces.ASGI,
log_level=LogLevels(loglevel.value), log_level=LogLevels(loglevel.value),
reload=True, reload=True,
reload_paths=get_reload_dirs(), reload_paths=get_reload_paths(),
reload_ignore_dirs=[".web", ".states"],
).serve() ).serve()
except ImportError: except ImportError:
console.error( console.error(

View File

@ -252,6 +252,7 @@ def test_env_var():
BLUBB: EnvVar[str] = env_var("default") BLUBB: EnvVar[str] = env_var("default")
INTERNAL: EnvVar[str] = env_var("default", internal=True) INTERNAL: EnvVar[str] = env_var("default", internal=True)
BOOLEAN: EnvVar[bool] = env_var(False) BOOLEAN: EnvVar[bool] = env_var(False)
LIST: EnvVar[list[int]] = env_var([1, 2, 3])
assert TestEnv.BLUBB.get() == "default" assert TestEnv.BLUBB.get() == "default"
assert TestEnv.BLUBB.name == "BLUBB" assert TestEnv.BLUBB.name == "BLUBB"
@ -280,3 +281,11 @@ def test_env_var():
assert TestEnv.BOOLEAN.get() is False assert TestEnv.BOOLEAN.get() is False
TestEnv.BOOLEAN.set(None) TestEnv.BOOLEAN.set(None)
assert "BOOLEAN" not in os.environ assert "BOOLEAN" not in os.environ
assert TestEnv.LIST.get() == [1, 2, 3]
assert TestEnv.LIST.name == "LIST"
TestEnv.LIST.set([4, 5, 6])
assert os.environ.get("LIST") == "4:5:6"
assert TestEnv.LIST.get() == [4, 5, 6]
TestEnv.LIST.set(None)
assert "LIST" not in os.environ