Merge branch 'main' into lendemor/add_PGH_rule

This commit is contained in:
Lendemor 2025-01-27 21:43:56 +01:00
commit 2cb39ae27f
11 changed files with 99 additions and 42 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ assets/external/*
dist/* dist/*
examples/ examples/
.web .web
.states
.idea .idea
.vscode .vscode
.coverage .coverage

View File

@ -408,7 +408,7 @@ export const connect = async (
socket.current = io(endpoint.href, { socket.current = io(endpoint.href, {
path: endpoint["pathname"], path: endpoint["pathname"],
transports: transports, transports: transports,
protocols: env.TEST_MODE ? undefined : [reflexEnvironment.version], protocols: [reflexEnvironment.version],
autoUnref: false, autoUnref: false,
}); });
// Ensure undefined fields in events are sent as null instead of removed // Ensure undefined fields in events are sent as null instead of removed

View File

@ -405,7 +405,31 @@ class App(MiddlewareMixin, LifespanMixin):
self.sio.register_namespace(self.event_namespace) self.sio.register_namespace(self.event_namespace)
# Mount the socket app with the API. # Mount the socket app with the API.
if self.api: if self.api:
self.api.mount(str(constants.Endpoint.EVENT), socket_app)
class HeaderMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
original_send = send
async def modified_send(message):
headers = dict(scope["headers"])
protocol_key = b"sec-websocket-protocol"
if (
message["type"] == "websocket.accept"
and protocol_key in headers
):
message["headers"] = [
*message.get("headers", []),
(b"sec-websocket-protocol", headers[protocol_key]),
]
return await original_send(message)
return await self.app(scope, receive, modified_send)
socket_app_with_headers = HeaderMiddleware(socket_app)
self.api.mount(str(constants.Endpoint.EVENT), socket_app_with_headers)
# Check the exception handlers # Check the exception handlers
self._validate_exception_handlers() self._validate_exception_handlers()

View File

@ -490,6 +490,9 @@ class EnvironmentVariables:
# The working directory for the next.js commands. # The working directory for the next.js commands.
REFLEX_WEB_WORKDIR: EnvVar[Path] = env_var(Path(constants.Dirs.WEB)) REFLEX_WEB_WORKDIR: EnvVar[Path] = env_var(Path(constants.Dirs.WEB))
# The working directory for the states directory.
REFLEX_STATES_WORKDIR: EnvVar[Path] = env_var(Path(constants.Dirs.STATES))
# Path to the alembic config file # Path to the alembic config file
ALEMBIC_CONFIG: EnvVar[ExistingPath] = env_var(Path(constants.ALEMBIC_CONFIG)) ALEMBIC_CONFIG: EnvVar[ExistingPath] = env_var(Path(constants.ALEMBIC_CONFIG))

View File

@ -52,7 +52,7 @@ class Dirs(SimpleNamespace):
# The name of the postcss config file. # The name of the postcss config file.
POSTCSS_JS = "postcss.config.js" POSTCSS_JS = "postcss.config.js"
# The name of the states directory. # The name of the states directory.
STATES = "states" STATES = ".states"
class Reflex(SimpleNamespace): class Reflex(SimpleNamespace):

View File

@ -39,7 +39,14 @@ class GitIgnore(SimpleNamespace):
# The gitignore file. # The gitignore file.
FILE = Path(".gitignore") FILE = Path(".gitignore")
# Files to gitignore. # Files to gitignore.
DEFAULTS = {Dirs.WEB, "*.db", "__pycache__/", "*.py[cod]", "assets/external/"} DEFAULTS = {
Dirs.WEB,
Dirs.STATES,
"*.db",
"__pycache__/",
"*.py[cod]",
"assets/external/",
}
class RequirementsTxt(SimpleNamespace): class RequirementsTxt(SimpleNamespace):

View File

@ -3046,7 +3046,7 @@ def is_serializable(value: Any) -> bool:
def reset_disk_state_manager(): def reset_disk_state_manager():
"""Reset the disk state manager.""" """Reset the disk state manager."""
states_directory = prerequisites.get_web_dir() / constants.Dirs.STATES states_directory = prerequisites.get_states_dir()
if states_directory.exists(): if states_directory.exists():
for path in states_directory.iterdir(): for path in states_directory.iterdir():
path.unlink() path.unlink()
@ -3094,7 +3094,7 @@ class StateManagerDisk(StateManager):
Returns: Returns:
The states directory. The states directory.
""" """
return prerequisites.get_web_dir() / constants.Dirs.STATES return prerequisites.get_states_dir()
def _purge_expired_states(self): def _purge_expired_states(self):
"""Purge expired states from the disk.""" """Purge expired states from the disk."""

View File

@ -307,7 +307,7 @@ def run_granian_backend(host, port, loglevel: LogLevel):
log_level=LogLevels(loglevel.value), log_level=LogLevels(loglevel.value),
reload=True, reload=True,
reload_paths=get_reload_dirs(), reload_paths=get_reload_dirs(),
reload_ignore_dirs=[".web"], reload_ignore_dirs=[".web", ".states"],
).serve() ).serve()
except ImportError: except ImportError:
console.error( console.error(
@ -467,19 +467,19 @@ def output_system_info():
system = platform.system() system = platform.system()
fnm_info = f"[FNM {prerequisites.get_fnm_version()} (Expected: {constants.Fnm.VERSION}) (PATH: {constants.Fnm.EXE})]"
if system != "Windows" or ( if system != "Windows" or (
system == "Windows" and prerequisites.is_windows_bun_supported() system == "Windows" and prerequisites.is_windows_bun_supported()
): ):
dependencies.extend( dependencies.extend(
[ [
f"[FNM {prerequisites.get_fnm_version()} (Expected: {constants.Fnm.VERSION}) (PATH: {constants.Fnm.EXE})]", fnm_info,
f"[Bun {prerequisites.get_bun_version()} (Expected: {constants.Bun.VERSION}) (PATH: {config.bun_path})]", f"[Bun {prerequisites.get_bun_version()} (Expected: {constants.Bun.VERSION}) (PATH: {path_ops.get_bun_path()})]",
], ],
) )
else: else:
dependencies.append( dependencies.append(fnm_info)
f"[FNM {prerequisites.get_fnm_version()} (Expected: {constants.Fnm.VERSION}) (PATH: {constants.Fnm.EXE})]",
)
if system == "Linux": if system == "Linux":
import distro import distro

View File

@ -9,7 +9,7 @@ import shutil
from pathlib import Path from pathlib import Path
from reflex import constants from reflex import constants
from reflex.config import environment from reflex.config import environment, get_config
# Shorthand for join. # Shorthand for join.
join = os.linesep.join join = os.linesep.join
@ -118,7 +118,7 @@ def ln(src: str | Path, dest: str | Path, overwrite: bool = False) -> bool:
return True return True
def which(program: str | Path) -> str | Path | None: def which(program: str | Path) -> Path | None:
"""Find the path to an executable. """Find the path to an executable.
Args: Args:
@ -127,7 +127,8 @@ def which(program: str | Path) -> str | Path | None:
Returns: Returns:
The path to the executable. The path to the executable.
""" """
return shutil.which(str(program)) which_result = shutil.which(program)
return Path(which_result) if which_result else None
def use_system_node() -> bool: def use_system_node() -> bool:
@ -156,12 +157,12 @@ def get_node_bin_path() -> Path | None:
""" """
bin_path = Path(constants.Node.BIN_PATH) bin_path = Path(constants.Node.BIN_PATH)
if not bin_path.exists(): if not bin_path.exists():
str_path = which("node") path = which("node")
return Path(str_path).parent.resolve() if str_path else None return path.parent.absolute() if path else None
return bin_path.resolve() return bin_path.absolute()
def get_node_path() -> str | None: def get_node_path() -> Path | None:
"""Get the node binary path. """Get the node binary path.
Returns: Returns:
@ -169,9 +170,8 @@ def get_node_path() -> str | None:
""" """
node_path = Path(constants.Node.PATH) node_path = Path(constants.Node.PATH)
if use_system_node() or not node_path.exists(): if use_system_node() or not node_path.exists():
system_node_path = which("node") node_path = which("node")
return str(system_node_path) if system_node_path else None return node_path
return str(node_path)
def get_npm_path() -> Path | None: def get_npm_path() -> Path | None:
@ -182,11 +182,22 @@ def get_npm_path() -> Path | None:
""" """
npm_path = Path(constants.Node.NPM_PATH) npm_path = Path(constants.Node.NPM_PATH)
if use_system_node() or not npm_path.exists(): if use_system_node() or not npm_path.exists():
system_npm_path = which("npm") npm_path = which("npm")
npm_path = Path(system_npm_path) if system_npm_path else None
return npm_path.absolute() if npm_path else None return npm_path.absolute() if npm_path else None
def get_bun_path() -> Path | None:
"""Get bun binary path.
Returns:
The path to the bun binary file.
"""
bun_path = get_config().bun_path
if use_system_bun() or not bun_path.exists():
bun_path = which("bun")
return bun_path.absolute() if bun_path else None
def update_json_file(file_path: str | Path, update_dict: dict[str, int | str]): def update_json_file(file_path: str | Path, update_dict: dict[str, int | str]):
"""Update the contents of a json file. """Update the contents of a json file.
@ -196,6 +207,9 @@ def update_json_file(file_path: str | Path, update_dict: dict[str, int | str]):
""" """
fp = Path(file_path) fp = Path(file_path)
# Create the parent directory if it doesn't exist.
fp.parent.mkdir(parents=True, exist_ok=True)
# Create the file if it doesn't exist. # Create the file if it doesn't exist.
fp.touch(exist_ok=True) fp.touch(exist_ok=True)

View File

@ -87,6 +87,17 @@ def get_web_dir() -> Path:
return environment.REFLEX_WEB_WORKDIR.get() return environment.REFLEX_WEB_WORKDIR.get()
def get_states_dir() -> Path:
"""Get the working directory for the states.
Can be overridden with REFLEX_STATES_WORKDIR.
Returns:
The working directory.
"""
return environment.REFLEX_STATES_WORKDIR.get()
def check_latest_package_version(package_name: str): def check_latest_package_version(package_name: str):
"""Check if the latest version of the package is installed. """Check if the latest version of the package is installed.
@ -194,10 +205,14 @@ def get_bun_version() -> version.Version | None:
Returns: Returns:
The version of bun. The version of bun.
""" """
bun_path = path_ops.get_bun_path()
if bun_path is None:
return None
try: try:
# Run the bun -v command and capture the output # Run the bun -v command and capture the output
result = processes.new_process([str(get_config().bun_path), "-v"], run=True) result = processes.new_process([str(get_config().bun_path), "-v"], run=True)
return version.parse(result.stdout) # pyright: ignore [reportArgumentType] result = processes.new_process([str(get_config().bun_path), "-v"], run=True)
return version.parse(str(result.stdout)) # pyright: ignore [reportArgumentType]
except FileNotFoundError: except FileNotFoundError:
return None return None
except version.InvalidVersion as e: except version.InvalidVersion as e:
@ -1051,9 +1066,7 @@ def install_bun():
) )
# Skip if bun is already installed. # Skip if bun is already installed.
if Path(get_config().bun_path).exists() and get_bun_version() == version.parse( if get_bun_version() == version.parse(constants.Bun.VERSION):
constants.Bun.VERSION
):
console.debug("Skipping bun installation as it is already installed.") console.debug("Skipping bun installation as it is already installed.")
return return
@ -1074,8 +1087,7 @@ def install_bun():
show_logs=console.is_debug(), show_logs=console.is_debug(),
) )
else: else:
unzip_path = path_ops.which("unzip") if path_ops.which("unzip") is None:
if unzip_path is None:
raise SystemPackageMissingError("unzip") raise SystemPackageMissingError("unzip")
# Run the bun install script. # Run the bun install script.
@ -1279,12 +1291,9 @@ def validate_bun():
Raises: Raises:
Exit: If custom specified bun does not exist or does not meet requirements. Exit: If custom specified bun does not exist or does not meet requirements.
""" """
# if a custom bun path is provided, make sure its valid bun_path = path_ops.get_bun_path()
# This is specific to non-FHS OS
bun_path = get_config().bun_path if bun_path and bun_path.samefile(constants.Bun.DEFAULT_PATH):
if path_ops.use_system_bun():
bun_path = path_ops.which("bun")
if bun_path != constants.Bun.DEFAULT_PATH:
console.info(f"Using custom Bun path: {bun_path}") console.info(f"Using custom Bun path: {bun_path}")
bun_version = get_bun_version() bun_version = get_bun_version()
if not bun_version: if not bun_version:

View File

@ -122,13 +122,13 @@ def test_validate_invalid_bun_path(mocker):
Args: Args:
mocker: Pytest mocker object. mocker: Pytest mocker object.
""" """
mock = mocker.Mock() mock_path = mocker.Mock()
mocker.patch.object(mock, "bun_path", return_value="/mock/path") mocker.patch("reflex.utils.path_ops.get_bun_path", return_value=mock_path)
mocker.patch("reflex.utils.prerequisites.get_config", mock)
mocker.patch("reflex.utils.prerequisites.get_bun_version", return_value=None) mocker.patch("reflex.utils.prerequisites.get_bun_version", return_value=None)
with pytest.raises(typer.Exit): with pytest.raises(typer.Exit):
prerequisites.validate_bun() prerequisites.validate_bun()
mock_path.samefile.assert_called_once()
def test_validate_bun_path_incompatible_version(mocker): def test_validate_bun_path_incompatible_version(mocker):
@ -137,9 +137,8 @@ def test_validate_bun_path_incompatible_version(mocker):
Args: Args:
mocker: Pytest mocker object. mocker: Pytest mocker object.
""" """
mock = mocker.Mock() mock_path = mocker.Mock()
mocker.patch.object(mock, "bun_path", return_value="/mock/path") mocker.patch("reflex.utils.path_ops.get_bun_path", return_value=mock_path)
mocker.patch("reflex.utils.prerequisites.get_config", mock)
mocker.patch( mocker.patch(
"reflex.utils.prerequisites.get_bun_version", "reflex.utils.prerequisites.get_bun_version",
return_value=version.parse("0.6.5"), return_value=version.parse("0.6.5"),