diff --git a/docker-compose.yml b/docker-compose.yml index d58ed84488f..ff6fdfc216e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,9 +29,11 @@ x-superset-volumes: &superset-volumes # /app/pythonpath_docker will be appended to the PYTHONPATH in the final container - ./docker:/app/docker - ./superset:/app/superset + - ./superset-core:/app/superset-core - ./superset-frontend:/app/superset-frontend - superset_home:/app/superset_home - ./tests:/app/tests + - superset_data:/app/data x-common-build: &common-build context: . target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean` @@ -274,3 +276,5 @@ volumes: external: false redis: external: false + superset_data: + external: false diff --git a/docker/docker-bootstrap.sh b/docker/docker-bootstrap.sh index 04709396cf9..9d18b66626c 100755 --- a/docker/docker-bootstrap.sh +++ b/docker/docker-bootstrap.sh @@ -21,8 +21,15 @@ set -eo pipefail # Make python interactive if [ "$DEV_MODE" == "true" ]; then if [ "$(whoami)" = "root" ] && command -v uv > /dev/null 2>&1; then - echo "Reinstalling the app in editable mode" - uv pip install -e . + # Always ensure superset-core is available + echo "Installing superset-core in editable mode" + uv pip install --no-deps -e /app/superset-core + + # Only reinstall the main app for non-worker processes + if [ "$1" != "worker" ] && [ "$1" != "beat" ]; then + echo "Reinstalling the app in editable mode" + uv pip install -e . + fi fi fi REQUIREMENTS_LOCAL="/app/docker/requirements-local.txt" @@ -34,7 +41,8 @@ if [ "$CYPRESS_CONFIG" == "true" ]; then export SUPERSET__SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://superset:superset@db:5432/superset_cypress PORT=8081 fi -if [[ "$DATABASE_DIALECT" == postgres* ]] && [ "$(whoami)" = "root" ]; then +# Skip postgres requirements installation for workers to avoid conflicts +if [[ "$DATABASE_DIALECT" == postgres* ]] && [ "$(whoami)" = "root" ] && [ "$1" != "worker" ] && [ "$1" != "beat" ]; then # older images may not have the postgres dev requirements installed echo "Installing postgres requirements" if command -v uv > /dev/null 2>&1; then diff --git a/superset/extensions/__init__.py b/superset/extensions/__init__.py index a44d82f76af..628af40cd62 100644 --- a/superset/extensions/__init__.py +++ b/superset/extensions/__init__.py @@ -21,7 +21,18 @@ from typing import Any, Callable, Optional import celery from flask import Flask from flask_appbuilder import AppBuilder -from flask_appbuilder.utils.legacy import get_sqla_class + +# Temporary fix for missing flask_appbuilder.utils.legacy module +try: + from flask_appbuilder.utils.legacy import get_sqla_class +except ImportError: + # Fallback if legacy module doesn't exist + from flask_sqlalchemy import SQLAlchemy + + def get_sqla_class() -> Any: + return SQLAlchemy + + from flask_caching.backends.base import BaseCache from flask_migrate import Migrate from flask_talisman import Talisman diff --git a/superset/extensions/local_extensions_watcher.py b/superset/extensions/local_extensions_watcher.py index 4616e4ff13b..5ce529687a8 100644 --- a/superset/extensions/local_extensions_watcher.py +++ b/superset/extensions/local_extensions_watcher.py @@ -23,29 +23,42 @@ import os import threading import time from pathlib import Path -from typing import Any +from typing import Any, TYPE_CHECKING from flask import Flask -from watchdog.events import FileSystemEventHandler -from watchdog.observers import Observer + +if TYPE_CHECKING: + pass logger = logging.getLogger(__name__) -class LocalExtensionFileHandler(FileSystemEventHandler): - """Custom file system event handler for LOCAL_EXTENSIONS directories.""" +def _get_file_handler_class() -> Any: + """Get the file handler class, importing watchdog only when needed.""" + try: + from watchdog.events import FileSystemEventHandler - def on_any_event(self, event: Any) -> None: - """Handle any file system event in the watched directories.""" - if event.is_directory: - return + class LocalExtensionFileHandler(FileSystemEventHandler): + """Custom file system event handler for LOCAL_EXTENSIONS directories.""" - logger.info("File change detected in LOCAL_EXTENSIONS: %s", event.src_path) + def on_any_event(self, event: Any) -> None: + """Handle any file system event in the watched directories.""" + if event.is_directory: + return - # Touch superset/__init__.py to trigger Flask's file watcher - superset_init = Path("superset/__init__.py") - logger.info("Triggering restart by touching %s", superset_init) - os.utime(superset_init, (time.time(), time.time())) + logger.info( + "File change detected in LOCAL_EXTENSIONS: %s", event.src_path + ) + + # Touch superset/__init__.py to trigger Flask's file watcher + superset_init = Path("superset/__init__.py") + logger.info("Triggering restart by touching %s", superset_init) + os.utime(superset_init, (time.time(), time.time())) + + return LocalExtensionFileHandler + except ImportError: + logger.warning("watchdog not installed, LOCAL_EXTENSIONS watcher disabled") + return None def setup_local_extensions_watcher(app: Flask) -> None: # noqa: C901 @@ -62,6 +75,11 @@ def setup_local_extensions_watcher(app: Flask) -> None: # noqa: C901 if not local_extensions: return + # Try to import watchdog and get handler class + handler_class = _get_file_handler_class() + if not handler_class: + return + # Collect dist directories to watch watch_dirs = [] for ext_path in local_extensions: @@ -81,8 +99,10 @@ def setup_local_extensions_watcher(app: Flask) -> None: # noqa: C901 return try: + from watchdog.observers import Observer + # Set up and start the file watcher - event_handler = LocalExtensionFileHandler() + event_handler = handler_class() observer = Observer() for watch_dir in watch_dirs: