eptm_dashboard/.venv/lib/python3.12/site-packages/reflex/utils/frontend_skeleton.py

309 lines
10 KiB
Python

"""This module provides utility functions to initialize the frontend skeleton."""
import json
import random
from pathlib import Path
from reflex_base import constants
from reflex_base.config import Config, get_config
from reflex_base.environment import environment
from reflex.compiler import templates
from reflex.utils import console, path_ops
from reflex.utils.prerequisites import get_project_hash, get_web_dir
from reflex.utils.registry import get_npm_registry
def initialize_gitignore(
gitignore_file: Path = constants.GitIgnore.FILE,
files_to_ignore: set[str] | list[str] = constants.GitIgnore.DEFAULTS,
):
"""Initialize the template .gitignore file.
Args:
gitignore_file: The .gitignore file to create.
files_to_ignore: The files to add to the .gitignore file.
"""
# Combine with the current ignored files.
current_ignore: list[str] = []
if gitignore_file.exists():
current_ignore = [ln.strip() for ln in gitignore_file.read_text().splitlines()]
if files_to_ignore == current_ignore:
console.debug(f"{gitignore_file} already up to date.")
return
files_to_ignore = [ln for ln in files_to_ignore if ln not in current_ignore]
files_to_ignore += current_ignore
# Write files to the .gitignore file.
gitignore_file.touch(exist_ok=True)
console.debug(f"Creating {gitignore_file}")
gitignore_file.write_text("\n".join(files_to_ignore) + "\n")
def _read_dependency_file(file_path: Path) -> tuple[str | None, str | None]:
"""Read a dependency file with a forgiving encoding strategy.
Args:
file_path: The file to read.
Returns:
A tuple of file content and the encoding used to read it.
"""
try:
return file_path.read_text(), None
except UnicodeDecodeError:
pass
except Exception as e:
console.error(f"Failed to read {file_path} due to {e}.")
raise SystemExit(1) from None
try:
return file_path.read_text(encoding="utf-8"), "utf-8"
except UnicodeDecodeError:
return None, None
except Exception as e:
console.error(f"Failed to read {file_path} due to {e}.")
raise SystemExit(1) from None
def _has_reflex_requirement_line(requirements_text: str) -> bool:
"""Check whether requirements.txt already contains reflex.
Returns:
Whether reflex is already present in the requirements text.
"""
return any(
_is_reflex_dependency_spec(line) for line in requirements_text.splitlines()
)
def _is_reflex_dependency_spec(requirement: str) -> bool:
"""Check whether a dependency specification refers to the reflex package.
Args:
requirement: The dependency specification to check.
Returns:
Whether the specification refers to the reflex package.
"""
requirement = requirement.strip()
if not requirement.lower().startswith("reflex"):
return False
suffix = requirement[len("reflex") :]
if suffix.startswith("["):
extras_end = suffix.find("]")
if extras_end == -1:
return False
suffix = suffix[extras_end + 1 :]
return not suffix or suffix.lstrip().startswith((
"==",
"!=",
">=",
"<=",
"~=",
">",
"<",
";",
"@",
))
def initialize_requirements_txt(
requirements_file_path: Path = Path(constants.RequirementsTxt.FILE),
pyproject_file_path: Path = Path(constants.PyprojectToml.FILE),
) -> bool:
"""Initialize the requirements.txt file.
If a project already uses pyproject.toml, leave dependency management to the
package manager. Otherwise ensure requirements.txt pins the current Reflex
version for legacy workflows.
Returns:
True if the user has to update the requirements.txt file.
"""
if not requirements_file_path.exists() and pyproject_file_path.exists():
return False
requirements_file_path.touch(exist_ok=True)
content, encoding = _read_dependency_file(requirements_file_path)
if content is None:
return True
if _has_reflex_requirement_line(content):
console.debug(f"{requirements_file_path} already has reflex as dependency.")
return False
console.debug(
f"Appending {constants.RequirementsTxt.DEFAULTS_STUB} to {requirements_file_path}"
)
with requirements_file_path.open("a", encoding=encoding) as f:
f.write(
"\n" + constants.RequirementsTxt.DEFAULTS_STUB + constants.Reflex.VERSION
)
return False
def initialize_web_directory():
"""Initialize the web directory on reflex init."""
console.log("Initializing the web directory.")
# Reuse the hash if one is already created, so we don't over-write it when running reflex init
project_hash = get_project_hash()
console.debug(f"Copying {constants.Templates.Dirs.WEB_TEMPLATE} to {get_web_dir()}")
path_ops.copy_tree(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
console.debug("Initializing the web directory.")
initialize_package_json()
console.debug("Initializing the bun config file.")
initialize_bun_config()
console.debug("Initializing the .npmrc file.")
initialize_npmrc()
console.debug("Initializing the public directory.")
path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
console.debug("Initializing the react-router.config.js file.")
update_react_router_config()
console.debug("Initializing the vite.config.js file.")
initialize_vite_config()
console.debug("Initializing the reflex.json file.")
# Initialize the reflex json file.
init_reflex_json(project_hash=project_hash)
def update_react_router_config(prerender_routes: bool = False):
"""Update react-router.config.js config from Reflex config.
Args:
prerender_routes: Whether to enable prerendering of routes.
"""
react_router_config_file_path = get_web_dir() / constants.ReactRouter.CONFIG_FILE
new_react_router_config = _update_react_router_config(
get_config(), prerender_routes=prerender_routes
)
# Overwriting the config file triggers a full server reload, so make sure
# there is actually a diff.
old_react_router_config = (
react_router_config_file_path.read_text()
if react_router_config_file_path.exists()
else ""
)
if old_react_router_config != new_react_router_config:
react_router_config_file_path.write_text(new_react_router_config)
def _update_react_router_config(config: Config, prerender_routes: bool = False):
react_router_config = {
"basename": config.prepend_frontend_path("/"),
"future": {
"unstable_optimizeDeps": True,
},
"ssr": False,
}
if prerender_routes:
react_router_config["prerender"] = True
react_router_config["build"] = constants.Dirs.BUILD_DIR
return f"export default {json.dumps(react_router_config)};"
def _compile_package_json():
return templates.package_json_template(
scripts={
"dev": constants.PackageJson.Commands.DEV,
"export": constants.PackageJson.Commands.EXPORT,
},
dependencies=constants.PackageJson.DEPENDENCIES,
dev_dependencies=constants.PackageJson.DEV_DEPENDENCIES,
overrides=constants.PackageJson.OVERRIDES,
)
def initialize_package_json():
"""Render and write in .web the package.json file."""
output_path = get_web_dir() / constants.PackageJson.PATH
output_path.write_text(_compile_package_json())
def _compile_vite_config(config: Config):
# base must have exactly one trailing slash
return templates.vite_config_template(
base=config.prepend_frontend_path("/"),
hmr=environment.VITE_HMR.get(),
force_full_reload=environment.VITE_FORCE_FULL_RELOAD.get(),
experimental_hmr=environment.VITE_EXPERIMENTAL_HMR.get(),
sourcemap=environment.VITE_SOURCEMAP.get(),
allowed_hosts=config.vite_allowed_hosts,
)
def initialize_vite_config():
"""Render and write in .web the vite.config.js file using Reflex config."""
vite_config_file_path = get_web_dir() / constants.ReactRouter.VITE_CONFIG_FILE
vite_config_file_path.write_text(_compile_vite_config(get_config()))
def initialize_bun_config():
"""Initialize the bun config file."""
bun_config_path = get_web_dir() / constants.Bun.CONFIG_PATH
if (custom_bunfig := Path(constants.Bun.CONFIG_PATH)).exists():
bunfig_content = custom_bunfig.read_text()
console.info(f"Copying custom bunfig.toml inside {get_web_dir()} folder")
else:
best_registry = get_npm_registry()
bunfig_content = constants.Bun.DEFAULT_CONFIG.format(registry=best_registry)
bun_config_path.write_text(bunfig_content)
def initialize_npmrc():
"""Initialize the .npmrc file."""
npmrc_path = get_web_dir() / constants.Node.CONFIG_PATH
if (custom_npmrc := Path(constants.Node.CONFIG_PATH)).exists():
npmrc_content = custom_npmrc.read_text()
console.info(f"Copying custom .npmrc inside {get_web_dir()} folder")
else:
best_registry = get_npm_registry()
npmrc_content = constants.Node.DEFAULT_CONFIG.format(registry=best_registry)
npmrc_path.write_text(npmrc_content)
def init_reflex_json(project_hash: int | None):
"""Write the hash of the Reflex project to a REFLEX_JSON.
Reuse the hash if one is already created, therefore do not
overwrite it every time we run the reflex init command
.
Args:
project_hash: The app hash.
"""
if project_hash is not None:
console.debug(f"Project hash is already set to {project_hash}.")
else:
# Get a random project hash.
project_hash = random.getrandbits(128)
console.debug(f"Setting project hash to {project_hash}.")
# Write the hash and version to the reflex json file.
reflex_json = {
"version": constants.Reflex.VERSION,
"project_hash": project_hash,
}
path_ops.update_json_file(get_web_dir() / constants.Reflex.JSON, reflex_json)