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

133 lines
4.5 KiB
Python

"""Miscellaneous functions for the experimental package."""
import asyncio
import contextlib
import inspect
import sys
import threading
from collections.abc import Callable
from pathlib import Path
from typing import Any
def get_module_path(module_name: str) -> Path | None:
"""Check if a module exists and return its path.
This function searches for a module by navigating through the module hierarchy
in each path of sys.path, checking for both .py files and packages with __init__.py.
Args:
module_name: The name of the module to search for (e.g., "package.submodule").
Returns:
The path to the module file if found, None otherwise.
"""
parts = module_name.split(".")
# Check each path in sys.path
for path in sys.path:
current_path = Path(path)
# Navigate through the module hierarchy
for i, part in enumerate(parts):
potential_file = current_path / (part + ".py")
potential_dir = current_path / part
if potential_file.is_file():
# We encountered a file, but we can't continue deeper
if i == len(parts) - 1:
return potential_file
return None # Can't continue deeper
if potential_dir.is_dir():
# It's a package, so we can continue deeper
current_path = potential_dir
else:
break # Path doesn't exist, break out of the loop
else:
return current_path / "__init__.py" # Made it through all parts
return None
async def run_in_thread(func: Callable) -> Any:
"""Run a function in a separate thread.
To not block the UI event queue, run_in_thread must be inside inside a rx.event(background=True) decorated method.
Args:
func: The non-async function to run.
Returns:
Any: The return value of the function.
Raises:
ValueError: If the function is an async function.
"""
if inspect.iscoroutinefunction(func):
msg = "func must be a non-async function"
raise ValueError(msg)
return await asyncio.get_event_loop().run_in_executor(None, func)
# Global lock for thread-safe sys.path manipulation
_sys_path_lock = threading.RLock()
@contextlib.contextmanager
def with_cwd_in_syspath():
"""Temporarily add current working directory to sys.path in a thread-safe manner.
This context manager temporarily prepends the current working directory to sys.path,
ensuring that modules in the current directory can be imported. The original sys.path
is restored when exiting the context.
Yields:
None
"""
with _sys_path_lock:
orig_sys_path = sys.path.copy()
sys.path.insert(0, str(Path.cwd()))
try:
yield
finally:
sys.path[:] = orig_sys_path
def preload_color_theme():
"""Create a script component that preloads the color theme to prevent FOUC.
This script runs immediately in the document head before React hydration,
reading the saved theme from localStorage and applying the correct CSS classes
to prevent flash of unstyled content.
Returns:
Script: A script component to add to App.head_components
"""
from reflex_components_core.el.elements.scripts import Script
# Create direct inline script content (like next-themes dangerouslySetInnerHTML)
script_content = """
// Only run in browser environment, not during SSR
if (typeof document !== 'undefined') {
try {
const theme = localStorage.getItem("theme") || "system";
const systemPreference = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
const resolvedTheme = theme === "system" ? systemPreference : theme;
// Apply theme immediately - blocks until complete
// Use classList to avoid overwriting other classes
document.documentElement.classList.remove("light", "dark");
document.documentElement.classList.add(resolvedTheme);
document.documentElement.style.colorScheme = resolvedTheme;
} catch (e) {
// Fallback to system preference on any error (resolve "system" to actual theme)
const fallbackTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.classList.remove("light", "dark");
document.documentElement.classList.add(fallbackTheme);
document.documentElement.style.colorScheme = fallbackTheme;
}
}
"""
return Script.create(script_content)