Skip to content
Open
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
127 changes: 127 additions & 0 deletions .github/workflows/python-metadata-compatibility.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: Python-Metadata-Compatibility

permissions:
contents: read

on:
pull_request:
paths:
- .github/workflows/python-metadata-compatibility.yml
- superset/db_engine_specs/**
- tests/unit_tests/db_engine_specs/test_lint_metadata.py
- pyproject.toml
- superset/result_set.py
- superset/common/utils/dataframe_utils.py
- tests/unit_tests/common/test_dataframe_utils.py
- superset/utils/core.py
- tests/unit_tests/utils/test_date_parsing.py
- superset/utils/pandas_postprocessing/**
- tests/unit_tests/pandas_postprocessing/**
- tests/unit_tests/result_set_test.py
- superset/commands/importers/v1/utils.py
- superset/commands/dataset/importers/v1/utils.py
- tests/unit_tests/commands/importers/v1/utils_test.py
- superset/models/helpers.py
- tests/unit_tests/models/helpers_test.py
- superset/superset_typing.py
- tests/unit_tests/db_engine_specs/test_base.py
- superset/extensions/metadb.py
- tests/unit_tests/extensions/test_sqlalchemy.py
- tests/unit_tests/conftest.py
- Dockerfile
- docker/**
- docker-compose-image-tag.yml
- scripts/docker_release_target.py
- tests/unit_tests/docker_app_dependencies_test.py
- tests/unit_tests/docker_compose_image_tag_test.py
- tests/unit_tests/scripts/docker_release_target_test.py
- superset-core/**
workflow_dispatch: {}

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
metadata-parser:
runs-on: ubuntu-26.04
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
python-version: [ "3.11", "3.12", "3.13", "3.14", "3.15" ]
env:
PYTEST_DISABLE_PLUGIN_AUTOLOAD: "1"
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install standalone test dependencies
run: python -m pip install pytest==7.4.4
- name: Test metadata parser without application dependencies
run: python -m pytest --noconftest -o addopts= tests/unit_tests/db_engine_specs/test_lint_metadata.py -q -W default::DeprecationWarning

runtime-compatibility:
runs-on: ubuntu-26.04
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
python-version: [ "3.11", "3.12", "3.13", "3.14", "3.15" ]
env:
SUPERSET_SECRET_KEY: python-compatibility-ci-only-not-for-production
FLASK_APP: superset
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Install application and focused test dependencies
run: >-
python -m pip install -e ./superset-core -e . pytest==7.4.4 pytest-mock freezegun parameterized responses rich prophet
- name: Check dependency consistency
run: python -m pip check
- name: Configure metadata directory
run: echo "SUPERSET_HOME=$RUNNER_TEMP/superset-python-compatibility" >> "$GITHUB_ENV"
- name: Migrate a fresh metadata database
run: python -m flask db upgrade
- name: Test application health and runtime compatibility
run: |
python - <<'PY'
import pytest
from superset.app import create_app

app = create_app()
assert app.test_client().get('/health').status_code == 200
with app.app_context():
raise SystemExit(pytest.main([
'--confcutdir=tests/unit_tests', '-p', 'pytest_mock',
'-o', 'addopts=',
'tests/unit_tests/db_engine_specs/test_lint_metadata.py',
Comment thread
luth2 marked this conversation as resolved.
Resolved
'tests/unit_tests/pandas_postprocessing',
'tests/unit_tests/result_set_test.py',
'tests/unit_tests/common/test_dataframe_utils.py',
'tests/unit_tests/utils/test_date_parsing.py',
'tests/unit_tests/commands/importers/v1/utils_test.py',
'tests/unit_tests/models/helpers_test.py',
Comment thread
luth2 marked this conversation as resolved.
Resolved
'tests/unit_tests/db_engine_specs/test_base.py::test_resolve_column_type_prefers_cursor_type',
'tests/unit_tests/db_engine_specs/test_base.py::test_fetch_data_no_result_set',
'tests/unit_tests/db_engine_specs/test_base.py::test_fetch_data_with_result_set',
'tests/unit_tests/extensions/test_sqlalchemy.py::test_superset_joins_with_limit_drops_fanout_matches',
'tests/unit_tests/docker_app_dependencies_test.py',
'tests/unit_tests/docker_compose_image_tag_test.py',
'tests/unit_tests/scripts/docker_release_target_test.py',
'superset-core/tests', '-q',
'-W', 'default::DeprecationWarning',
]))
PY
10 changes: 9 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,15 @@ COPY requirements/base.txt requirements/
COPY superset-core superset-core

RUN --mount=type=cache,target=${SUPERSET_HOME}/.cache/uv \
/app/docker/pip-install.sh --requires-build-essential -r requirements/base.txt
if python -c 'import sys; raise SystemExit(sys.version_info >= (3, 15))'; then \
/app/docker/install-app-dependencies.sh /app; \
fi

RUN --mount=type=cache,target=${SUPERSET_HOME}/.cache/uv \
--mount=type=bind,source=.,target=/src \
Comment thread
luth2 marked this conversation as resolved.
Resolved
if python -c 'import sys; raise SystemExit(sys.version_info < (3, 15))'; then \
/app/docker/install-app-dependencies.sh /src; \
fi

# Copy compiled frontend assets and application source now that
# dependencies have been resolved and cached above.
Expand Down
33 changes: 33 additions & 0 deletions docker/install-app-dependencies.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
Comment thread
luth2 marked this conversation as resolved.
Resolved
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
set -euo pipefail

SOURCE_ROOT="${1:?Application source root is required}"

if python -c 'import sys; raise SystemExit(sys.version_info < (3, 15))'; then
if [[ -d "${SOURCE_ROOT}" && ! -w "${SOURCE_ROOT}" ]]; then
BUILD_ROOT="$(mktemp -d)"
trap 'rm -rf "${BUILD_ROOT}"' EXIT
cp -a "${SOURCE_ROOT}/." "${BUILD_ROOT}/"
SOURCE_ROOT="${BUILD_ROOT}"
fi
/app/docker/pip-install.sh --requires-build-essential \
--no-sources "${SOURCE_ROOT}/superset-core" "${SOURCE_ROOT}"
else
/app/docker/pip-install.sh --requires-build-essential -r requirements/base.txt
fi
69 changes: 69 additions & 0 deletions docs/admin_docs/installation/pypi.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,75 @@ import useBaseUrl from '@docusaurus/useBaseUrl';

This page describes how to install Superset using the `apache_superset` package [published on PyPI](https://pypi.org/project/apache_superset/).

## Python Compatibility

The minimum Python version is 3.11. The Python version classifiers in
[pyproject.toml](https://github.com/apache/superset/blob/master/pyproject.toml)
list the officially supported versions; the open-ended `requires-python` lower
bound does not certify every newer interpreter.

The source checkout includes experimental Python 3.15 compatibility. Its package
requirements select NumPy `>=2.5.4,<2.6` and pandas `>=3.1.0,<3.2` on Python 3.15
and newer. Python 3.11 through 3.14 retain NumPy `>=1.23.5,<2.5` and pandas
`>=3.0.6,<3.1`; the minimum Python version has not changed. These source changes
do not imply that an already published PyPI release supports Python 3.15.

The focused
[compatibility workflow](https://github.com/apache/superset/blob/master/.github/workflows/python-metadata-compatibility.yml)
tests Python 3.11 through 3.15. It installs the application without dependency
overrides, checks dependency consistency, migrates a fresh SQLite metadata
database, checks `/health`, and runs engine metadata, pandas post-processing
(including Prophet), result-set, importer utility, model helper, cursor, join,
Docker configuration, and core SDK tests. This focused selection does not
establish support for every optional database or feature.

For Python 3.15 testing, install from the source checkout's package metadata,
rather than applying the generated Python 3.11 requirement pins:

```bash
python -m pip install -e ./superset-core -e .
```

Run this command from the repository root in an isolated Python 3.15 environment.
Some dependencies require source builds, so a working C/C++ toolchain may be
needed. On Windows, install compatible C/C++ build tools. The
generated requirement files remain tied to the existing Python 3.11 deployment
baseline and are not Python 3.15 lock files.

When building the repository's `lean` or `superset` Docker target with a Python
3.15 base image, the application dependency stage installs both local packages
from their metadata instead of applying the Python 3.11 pins. Older interpreters
retain the requirements-based installation. This does not change the default
image's Python version or establish Python 3.15 support for every Docker target;
the development target still uses its generated development requirements.

Container acceptance must include the complete image build, dependency consistency,
fresh metadata migration, authentication, and the workloads needed by the deployment.
A complete Python 3.15 image build has not been verified by this focused workflow;
a dependency-only image build is not a substitute for that acceptance. The image's
`/health` endpoint is a liveness check, not proof that PostgreSQL, Redis, or queued
work is available; verify those workflows separately in readiness monitoring.

For deployment-specific shutdown validation, send SIGTERM to the Gunicorn master
and allow more than its configured graceful timeout before escalating. Validate
worker exits as well as the master's exit code. Gunicorn gthread workers can
encounter a keepalive-drain issue requiring deadline escalation after requests.
An alternative to evaluate for the deployment is
`SERVER_WORKER_CLASS=sync` with `SERVER_THREADS_AMOUNT=1`; setting only the worker
class while retaining multiple threads can select gthread again. Synchronous
workers have different concurrency characteristics, so capacity must be measured
for that configuration.

The focused workflow is not a full production certification. The full
database-driver matrix, integration and browser workflows, live report delivery,
and screenshot generation require deployment-specific validation. Production
acceptance must also cover the target deployment's authentication, secrets,
network/TLS configuration, workload, backup/restore, and availability requirements.
Windows does not provide extended `longdouble` precision and has Unix-signal
type-checking limitations.
Use an officially supported Python version for production deployments until
these broader compatibility checks have been completed.

## OS Dependencies

Superset stores database connection information in its metadata database. For that purpose, we use
Expand Down
20 changes: 1 addition & 19 deletions docs/static/resources/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -23732,15 +23732,6 @@
"schema": {
"type": "string"
}
},
{
"description": "Table schema",
"in": "path",
"name": "schema_name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
Expand Down Expand Up @@ -23778,7 +23769,7 @@
"api_key": []
}
],
"summary": "Get database select star for table",
"summary": "Get database select star for table in the default schema",
"tags": [
"Database"
]
Expand Down Expand Up @@ -27758,15 +27749,6 @@
"/api/v1/log/recent_activity/": {
"get": {
"parameters": [
{
"description": "The id of the user",
"in": "path",
"name": "user_id",
"required": true,
"schema": {
"type": "integer"
}
},
{
"content": {
"application/json": {
Expand Down
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,13 @@ dependencies = [
"marshmallow-union>=0.1.15.post1",
"msgpack>=1.2.3, <1.3",
"nh3>=0.3.7, <0.4",
"numpy>=1.23.5, <2.5",
"numpy>=1.23.5, <2.5; python_version < '3.15'",
"numpy>=2.5.4, <2.6; python_version >= '3.15'",
"packaging",
# --------------------------
# pandas and related (wanting pandas[performance] without numba as it's 100+MB and not needed)
"pandas[excel]>=3.0.6, <3.1",
"pandas[excel]>=3.0.6, <3.1; python_version < '3.15'",
"pandas[excel]>=3.1.0, <3.2; python_version >= '3.15'",
Comment thread
luth2 marked this conversation as resolved.
Resolved
Comment thread
luth2 marked this conversation as resolved.
Resolved
"bottleneck", # recommended performance dependency for pandas, see https://pandas.pydata.org/docs/getting_started/install.html#performance-dependencies-recommended
# --------------------------
"parsedatetime",
Expand All @@ -104,6 +106,7 @@ dependencies = [
"PyJWT>=2.15.1, <3.0", # raise floor to match resolved pin; closes SCA false-positive on 2.4.x-range CVEs already fixed in 2.13.0
"redis>=5.0.0, <9.0",
"rison>=2.0.1, <3.0",
"rich",

"shillelagh[gsheetsapi]>=1.4.5, <2.0",
"sshtunnel>=0.5.1, <0.6",
Expand Down Expand Up @@ -538,6 +541,7 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
"superset/translations/utils.py" = ["TID251"]
"superset/extensions/__init__.py" = ["TID251"]
"superset/utils/json.py" = ["TID251"]
"tests/unit_tests/docker_app_dependencies_test.py" = ["S603"]
"docker/*" = ["I"] # Docker config files have non-standard imports that vary by environment
"superset/db_engine_specs/lib.py" = ["E501"] # Database config file with long description strings

Expand Down
51 changes: 25 additions & 26 deletions superset/commands/dataset/importers/v1/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -986,32 +986,31 @@ def _convert_temporal_columns(df: pd.DataFrame, dtype: dict[str, Any]) -> None:
for column_name, sqla_type in dtype.items():
if isinstance(sqla_type, (Date, DateTime)):
try:
df[column_name] = pd.to_datetime(df[column_name])
except OutOfBoundsDatetime:
# Row-level fallback: coerce only OOB values; re-raise for malformed
# strings. Whole-column errors="coerce" would silently swallow
# malformed values that happen to share a column with an OOB date.
original = df[column_name].copy()
result = []
for val in original:
if pd.isna(val):
result.append(pd.NaT)
continue
try:
result.append(pd.to_datetime(val))
except OutOfBoundsDatetime:
result.append(pd.NaT)
# Other exceptions (e.g. malformed strings) propagate
converted = pd.Series(result, index=original.index)
n_coerced = int(converted.isna().sum() - original.isna().sum())
if n_coerced > 0:
logger.warning(
"Coerced %d out-of-bounds datetime value(s) "
"in column '%s' to NaT",
n_coerced,
column_name,
)
df[column_name] = converted
converted = pd.to_datetime(df[column_name])
if pd.api.types.is_datetime64_any_dtype(converted):
df[column_name] = converted
continue
except ValueError:
pass
original = df[column_name].copy()
result = []
for val in original:
if pd.isna(val):
result.append(pd.NaT)
continue
try:
result.append(pd.to_datetime(val))
except OutOfBoundsDatetime:
result.append(pd.NaT)
converted = pd.Series(result, index=original.index)
n_coerced = int(converted.isna().sum() - original.isna().sum())
if n_coerced > 0:
logger.warning(
"Coerced %d out-of-bounds datetime value(s) in column '%s' to NaT",
n_coerced,
column_name,
)
df[column_name] = converted


def _read_bounded(stream: Any, max_bytes: int) -> io.BytesIO:
Expand Down
Loading