Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -274,3 +276,5 @@ volumes:
external: false
redis:
external: false
superset_data:
external: false
14 changes: 11 additions & 3 deletions docker/docker-bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
13 changes: 12 additions & 1 deletion superset/extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,18 @@
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
Expand Down
50 changes: 35 additions & 15 deletions superset/extensions/local_extensions_watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,42 @@
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

class LocalExtensionFileHandler(FileSystemEventHandler):
"""Custom file system event handler for LOCAL_EXTENSIONS directories."""

def on_any_event(self, event: Any) -> None:
"""Handle any file system event in the watched directories."""
if event.is_directory:
return

def on_any_event(self, event: Any) -> None:
"""Handle any file system event in the watched directories."""
if event.is_directory:
return
logger.info(
"File change detected in LOCAL_EXTENSIONS: %s", event.src_path
)

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()))

# 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
Expand All @@ -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:
Expand All @@ -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:
Expand Down
Loading