mirror of
https://github.com/apache/superset.git
synced 2026-08-12 11:11:01 +00:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e32a683f6f | ||
|
|
362b9faaab | ||
|
|
fdd97d42ad | ||
|
|
1d276e67b2 | ||
|
|
ed15be88d9 | ||
|
|
d203f0de33 | ||
|
|
a75f9b67b2 | ||
|
|
3f0858e35d | ||
|
|
68c145adc3 | ||
|
|
4a9aecda4a | ||
|
|
46b2d7d7a9 | ||
|
|
f8600471fa |
@@ -120,19 +120,6 @@ repos:
|
||||
entry: ruff check --fix --show-fixes
|
||||
language: system
|
||||
types: [python]
|
||||
- id: ruff-import-placement
|
||||
name: ruff (import placement / PLC0415)
|
||||
# PLC0415 ("import should be at top-level") is preview-only in
|
||||
# ruff, so we can't put it in `[tool.ruff.lint] select` without
|
||||
# enabling preview mode globally (which would also activate
|
||||
# behavior changes for unrelated stable rules). Run it as a
|
||||
# dedicated step instead. Existing function-body imports are
|
||||
# grandfathered with per-line `# noqa: PLC0415`; new code must
|
||||
# either move the import to the top or add the same noqa with
|
||||
# a justification.
|
||||
entry: ruff check --select PLC0415 --preview --no-fix
|
||||
language: system
|
||||
types: [python]
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pylint
|
||||
|
||||
+1
-34
@@ -341,13 +341,6 @@ target-version = "py310"
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
||||
# McCabe complexity (`C901`) by default.
|
||||
#
|
||||
# NOTE: PLC0415 (`import` should be at the top-level of a file) is enforced
|
||||
# via a dedicated pre-commit hook (`ruff-import-placement`) rather than
|
||||
# globally here, because the rule is still preview-only in ruff and
|
||||
# enabling `preview = true` globally would also activate other preview-
|
||||
# rule behavior changes we don't want. Existing function-body imports
|
||||
# have been grandfathered with per-line `# noqa: PLC0415`.
|
||||
select = [
|
||||
"B904",
|
||||
"E4",
|
||||
@@ -397,37 +390,11 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
"superset/cli/update.py" = ["TID251"]
|
||||
"superset/key_value/types.py" = ["TID251"]
|
||||
"superset/translations/utils.py" = ["TID251"]
|
||||
"superset/extensions/__init__.py" = ["TID251"]
|
||||
"superset/utils/json.py" = ["TID251"]
|
||||
"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
|
||||
|
||||
# PLC0415 — function-body imports. Allow in directories where this is a
|
||||
# deliberate pattern rather than an oversight:
|
||||
# - cli/, scripts/: subcommand-deferred imports (don't load heavy modules
|
||||
# unless the subcommand actually runs).
|
||||
# - tasks/: Celery task bodies routinely defer imports of the modules
|
||||
# they orchestrate.
|
||||
# - migrations/versions/: Alembic migrations import models at runtime
|
||||
# to interact with the schema state, not at module load.
|
||||
# - mcp_service/: MCP tools lazy-load resources on invocation so the
|
||||
# server can register many tools without paying their import cost.
|
||||
# - db_engine_specs/: engine specs defer driver imports so optional
|
||||
# DB drivers don't have to be installed.
|
||||
# - initialization/__init__.py, extensions/__init__.py: the app-factory
|
||||
# and extension wiring are full of intentional circular-import
|
||||
# workarounds.
|
||||
"superset/cli/**/*.py" = ["PLC0415"]
|
||||
"scripts/**/*.py" = ["PLC0415"]
|
||||
"superset/tasks/**/*.py" = ["PLC0415"]
|
||||
"superset/migrations/versions/**/*.py" = ["PLC0415"]
|
||||
"superset/mcp_service/**/*.py" = ["PLC0415"]
|
||||
"superset/db_engine_specs/**/*.py" = ["PLC0415"]
|
||||
"superset/initialization/__init__.py" = ["PLC0415"]
|
||||
"superset/extensions/__init__.py" = ["TID251", "PLC0415"]
|
||||
"superset/app.py" = ["PLC0415"]
|
||||
"tests/**/*.py" = ["PLC0415"] # Tests import fixtures lazily; rule still
|
||||
# applies in CI to production code in src/.
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
case-sensitive = false
|
||||
combine-as-imports = true
|
||||
|
||||
@@ -92,6 +92,26 @@ class Dimension:
|
||||
grain: Grain | None = None
|
||||
|
||||
|
||||
class AggregationType(str, enum.Enum):
|
||||
"""
|
||||
Aggregation function applied by a metric.
|
||||
|
||||
Additivity (across an arbitrary set of grouping dimensions):
|
||||
* ``SUM``, ``COUNT``: fully additive — sub-group sums roll up via ``sum``.
|
||||
* ``MIN``, ``MAX``: roll up via ``min`` / ``max`` of sub-group values.
|
||||
* ``AVG``, ``COUNT_DISTINCT``, ``OTHER``: not safely roll-uppable from
|
||||
sub-aggregates without auxiliary data.
|
||||
"""
|
||||
|
||||
SUM = "SUM"
|
||||
COUNT = "COUNT"
|
||||
MIN = "MIN"
|
||||
MAX = "MAX"
|
||||
AVG = "AVG"
|
||||
COUNT_DISTINCT = "COUNT_DISTINCT"
|
||||
OTHER = "OTHER"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Metric:
|
||||
id: str
|
||||
@@ -100,6 +120,7 @@ class Metric:
|
||||
|
||||
definition: str
|
||||
description: str | None = None
|
||||
aggregation: AggregationType | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -87,7 +87,7 @@ def extension_setup_for_dev():
|
||||
"""Set up extension structure for dev testing."""
|
||||
|
||||
def _setup(base_path: Path) -> None:
|
||||
import json # noqa: PLC0415
|
||||
import json
|
||||
|
||||
# Create extension.json with new structure
|
||||
extension_json = {
|
||||
@@ -111,7 +111,7 @@ def extension_setup_for_bundling():
|
||||
"""Set up a complete extension structure ready for bundling."""
|
||||
|
||||
def _setup(base_path: Path) -> None:
|
||||
import json # noqa: PLC0415
|
||||
import json
|
||||
|
||||
# Create dist directory with manifest and files
|
||||
dist_dir = base_path / "dist"
|
||||
|
||||
@@ -347,7 +347,7 @@ def test_build_manifest_exits_when_extension_json_missing(isolated_filesystem):
|
||||
@pytest.mark.unit
|
||||
def test_clean_dist_frontend_removes_frontend_dist(isolated_filesystem):
|
||||
"""Test clean_dist_frontend removes frontend/dist directory specifically."""
|
||||
from superset_extensions_cli.cli import clean_dist_frontend # noqa: PLC0415
|
||||
from superset_extensions_cli.cli import clean_dist_frontend
|
||||
|
||||
# Create dist/frontend structure
|
||||
dist_dir = isolated_filesystem / "dist"
|
||||
@@ -366,7 +366,7 @@ def test_clean_dist_frontend_removes_frontend_dist(isolated_filesystem):
|
||||
@pytest.mark.unit
|
||||
def test_clean_dist_frontend_handles_nonexistent_directory(isolated_filesystem):
|
||||
"""Test clean_dist_frontend handles case where frontend dist doesn't exist."""
|
||||
from superset_extensions_cli.cli import clean_dist_frontend # noqa: PLC0415
|
||||
from superset_extensions_cli.cli import clean_dist_frontend
|
||||
|
||||
# No dist directory exists
|
||||
clean_dist_frontend(isolated_filesystem)
|
||||
@@ -377,7 +377,7 @@ def test_clean_dist_frontend_handles_nonexistent_directory(isolated_filesystem):
|
||||
@pytest.mark.unit
|
||||
def test_run_frontend_build_with_output_messages(isolated_filesystem):
|
||||
"""Test run_frontend_build produces expected output messages."""
|
||||
from superset_extensions_cli.cli import run_frontend_build # noqa: PLC0415
|
||||
from superset_extensions_cli.cli import run_frontend_build
|
||||
|
||||
frontend_dir = isolated_filesystem / "frontend"
|
||||
frontend_dir.mkdir()
|
||||
@@ -406,7 +406,7 @@ def test_rebuild_frontend_handles_build_results(
|
||||
isolated_filesystem, return_code, expected_result
|
||||
):
|
||||
"""Test rebuild_frontend handles different build results."""
|
||||
from superset_extensions_cli.cli import rebuild_frontend # noqa: PLC0415
|
||||
from superset_extensions_cli.cli import rebuild_frontend
|
||||
|
||||
# Create frontend structure
|
||||
frontend_dir = isolated_filesystem / "frontend"
|
||||
@@ -434,7 +434,7 @@ def test_rebuild_frontend_handles_build_results(
|
||||
@pytest.mark.unit
|
||||
def test_rebuild_backend_calls_copy_and_shows_message(isolated_filesystem):
|
||||
"""Test rebuild_backend calls copy_backend_files and shows success message."""
|
||||
from superset_extensions_cli.cli import rebuild_backend # noqa: PLC0415
|
||||
from superset_extensions_cli.cli import rebuild_backend
|
||||
|
||||
# Create extension.json
|
||||
extension_json = {
|
||||
|
||||
@@ -35,7 +35,7 @@ def test_validate_command_success(cli_runner, isolated_filesystem):
|
||||
"version": "1.0.0",
|
||||
"permissions": [],
|
||||
}
|
||||
import json # noqa: PLC0415
|
||||
import json
|
||||
|
||||
(isolated_filesystem / "extension.json").write_text(json.dumps(extension_json))
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ def test_template_rendering_with_different_ids(
|
||||
jinja_env, publisher, technical_name, display_name
|
||||
):
|
||||
"""Test templates render correctly with various publisher/name combinations."""
|
||||
from superset_extensions_cli.utils import ( # noqa: PLC0415
|
||||
from superset_extensions_cli.utils import (
|
||||
get_module_federation_name,
|
||||
kebab_to_snake_case,
|
||||
)
|
||||
|
||||
@@ -95,8 +95,11 @@ class FakeMessageChannel {
|
||||
const port2 = new FakeMessagePort();
|
||||
port1.otherPort = port2;
|
||||
port2.otherPort = port1;
|
||||
this.port1 = port1;
|
||||
this.port2 = port2;
|
||||
// FakeMessagePort only implements the subset of MessagePort that
|
||||
// Switchboard exercises; cast at the boundary so the fake satisfies
|
||||
// the consumer signature without weakening the production type.
|
||||
this.port1 = port1 as unknown as MessagePort;
|
||||
this.port2 = port2 as unknown as MessagePort;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ function isError(message: Message): message is ErrorMessage {
|
||||
* Calling methods on the switchboard causes messages to be sent through the channel.
|
||||
*/
|
||||
export class Switchboard {
|
||||
port: MessagePort;
|
||||
port!: MessagePort;
|
||||
|
||||
name = '';
|
||||
|
||||
@@ -97,9 +97,9 @@ export class Switchboard {
|
||||
// used to make unique ids
|
||||
incrementor = 1;
|
||||
|
||||
debugMode: boolean;
|
||||
debugMode = false;
|
||||
|
||||
private isInitialised: boolean;
|
||||
private isInitialised = false;
|
||||
|
||||
constructor(params?: Params) {
|
||||
if (!params) {
|
||||
|
||||
@@ -1671,7 +1671,7 @@ export interface VizOptions {
|
||||
|
||||
export function createDatasource(
|
||||
vizOptions: VizOptions,
|
||||
): SqlLabThunkAction<Promise<unknown>> {
|
||||
): SqlLabThunkAction<Promise<{ id: number }>> {
|
||||
return (dispatch: AppDispatch) => {
|
||||
dispatch(createDatasourceStarted());
|
||||
const { dbId, catalog, schema, datasourceName, sql, templateParams } =
|
||||
@@ -1691,9 +1691,10 @@ export function createDatasource(
|
||||
}),
|
||||
})
|
||||
.then(({ json }) => {
|
||||
dispatch(createDatasourceSuccess(json as { id: number }));
|
||||
const result = json as { id: number };
|
||||
dispatch(createDatasourceSuccess(result));
|
||||
|
||||
return Promise.resolve(json);
|
||||
return result;
|
||||
})
|
||||
.catch(error => {
|
||||
getClientErrorObject(error).then(e => {
|
||||
@@ -1712,7 +1713,7 @@ export function createDatasource(
|
||||
|
||||
export function createCtasDatasource(
|
||||
vizOptions: Record<string, unknown>,
|
||||
): SqlLabThunkAction<Promise<{ id: number }>> {
|
||||
): SqlLabThunkAction<Promise<{ table_id: number }>> {
|
||||
return (dispatch: AppDispatch) => {
|
||||
dispatch(createDatasourceStarted());
|
||||
return SupersetClient.post({
|
||||
@@ -1720,9 +1721,14 @@ export function createCtasDatasource(
|
||||
jsonPayload: vizOptions,
|
||||
})
|
||||
.then(({ json }) => {
|
||||
dispatch(createDatasourceSuccess(json.result));
|
||||
const result = json.result as { table_id: number };
|
||||
// The endpoint's `result.table_id` IS the dataset id; normalize so
|
||||
// createDatasourceSuccess's `${data.id}__table` resolves correctly.
|
||||
// Without this, the CTAS Explore button silently produced
|
||||
// `"undefined__table"` because `result.id` doesn't exist.
|
||||
dispatch(createDatasourceSuccess({ id: result.table_id }));
|
||||
|
||||
return json.result;
|
||||
return result;
|
||||
})
|
||||
.catch(() => {
|
||||
const errorMsg = t('An error occurred while creating the data source');
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
|
||||
import { useRef, useEffect, FC, useMemo } from 'react';
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import { logging } from '@apache-superset/core/utils';
|
||||
import {
|
||||
SqlLabRootState,
|
||||
@@ -86,7 +87,7 @@ const EditorAutoSync: FC = () => {
|
||||
const editorTabLastUpdatedAt = useSelector<SqlLabRootState, number>(
|
||||
state => state.sqlLab.editorTabLastUpdatedAt,
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const lastSavedTimestampRef = useRef<number>(editorTabLastUpdatedAt);
|
||||
|
||||
const currentQueryEditorId = useSelector<SqlLabRootState, string>(
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import { usePrevious } from '@superset-ui/core';
|
||||
import { css, useTheme } from '@apache-superset/core/theme';
|
||||
import { Global } from '@emotion/react';
|
||||
@@ -136,7 +137,7 @@ const EditorWrapper = ({
|
||||
height,
|
||||
hotkeys,
|
||||
}: EditorWrapperProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const queryEditor = useQueryEditor(queryEditorId, [
|
||||
'id',
|
||||
'dbId',
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { useDispatch, useStore } from 'react-redux';
|
||||
import { useStore } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { getExtensionsRegistry } from '@superset-ui/core';
|
||||
|
||||
@@ -68,7 +69,7 @@ export function useKeywords(
|
||||
catalog,
|
||||
schema,
|
||||
});
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const hasFetchedKeywords = useRef(false);
|
||||
// skipFetch is used to prevent re-evaluating memoized keywords
|
||||
// due to updated api results by skip flag
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { JsonObject, VizType } from '@superset-ui/core';
|
||||
import { VizType } from '@superset-ui/core';
|
||||
import {
|
||||
createCtasDatasource,
|
||||
addInfoToast,
|
||||
@@ -45,7 +46,7 @@ const ExploreCtasResultsButton = ({
|
||||
const errorMessage = useSelector(
|
||||
(state: SqlLabRootState) => state.sqlLab.errorMessage,
|
||||
);
|
||||
const dispatch = useDispatch<(dispatch: any) => Promise<JsonObject>>();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const buildVizOptions = {
|
||||
table_name: table,
|
||||
@@ -56,7 +57,7 @@ const ExploreCtasResultsButton = ({
|
||||
|
||||
const visualize = () => {
|
||||
dispatch(createCtasDatasource(buildVizOptions))
|
||||
.then((data: { table_id: number }) => {
|
||||
.then(data => {
|
||||
const formData = {
|
||||
datasource: `${data.table_id}__table`,
|
||||
metrics: ['count'],
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import URI from 'urijs';
|
||||
import { pick } from 'lodash';
|
||||
import { useComponentDidUpdate } from '@superset-ui/core';
|
||||
@@ -49,7 +50,7 @@ const PopEditorTab: React.FC<{ children?: React.ReactNode }> = ({
|
||||
({ sqlLab: { tabHistory } }) => tabHistory.slice(-1)[0],
|
||||
);
|
||||
const [updatedUrl, setUpdatedUrl] = useState<string>(SQL_LAB_URL);
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
useComponentDidUpdate(() => {
|
||||
setQueryEditorId(assigned => assigned ?? activeQueryEditorId);
|
||||
if (activeQueryEditorId) {
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useRef } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import { isObject } from 'lodash';
|
||||
import rison from 'rison';
|
||||
import {
|
||||
@@ -82,7 +83,7 @@ function QueryAutoRefresh({
|
||||
.map(({ id }) => id),
|
||||
),
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const checkForRefresh = () => {
|
||||
const shouldRequestChecking = shouldCheckForQueries(queries);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { Dropdown, Button } from '@superset-ui/core/components';
|
||||
import { Menu } from '@superset-ui/core/components/Menu';
|
||||
@@ -75,7 +75,7 @@ const QueryLimitSelect = ({
|
||||
maxRow,
|
||||
defaultQueryLimit,
|
||||
}: QueryLimitSelectProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const queryEditor = useQueryEditor(queryEditorId, ['id', 'queryLimit']);
|
||||
const queryLimit = queryEditor.queryLimit || defaultQueryLimit;
|
||||
|
||||
@@ -30,7 +30,8 @@ import ProgressBar from '@superset-ui/core/components/ProgressBar';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { QueryResponse, QueryState } from '@superset-ui/core';
|
||||
import { useTheme } from '@apache-superset/core/theme';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
|
||||
import {
|
||||
queryEditorSetSql,
|
||||
@@ -92,7 +93,7 @@ const QueryTable = ({
|
||||
latestQueryId,
|
||||
}: QueryTableProps) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const [selectedQuery, setSelectedQuery] = useState<QueryResponse | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
@@ -27,7 +27,8 @@ import {
|
||||
} from 'react';
|
||||
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { pick } from 'lodash';
|
||||
import {
|
||||
@@ -231,7 +232,7 @@ const ResultSet = ({
|
||||
canCopyClipboardSqlLab: canCopyClipboard,
|
||||
} = usePermissions();
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const logAction = useLogAction({ queryId, sqlEditorId: query.sqlEditorId });
|
||||
const { showConfirm, ConfirmModal } = useConfirmModal();
|
||||
|
||||
|
||||
+38
-67
@@ -16,8 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import * as reactRedux from 'react-redux';
|
||||
import { act } from 'react';
|
||||
import { act, type ComponentProps } from 'react';
|
||||
import {
|
||||
cleanup,
|
||||
fireEvent,
|
||||
@@ -40,6 +39,19 @@ const mockedProps = {
|
||||
datasource: testQuery,
|
||||
};
|
||||
|
||||
// Render with the SqlLab user fixture preloaded into the mock store so the
|
||||
// component's useSelector(state => state.user) returns a useful value.
|
||||
// Previously this test used jest.spyOn(reactRedux, 'useSelector') to inject
|
||||
// the user directly, which can't intercept calls routed through the typed
|
||||
// useAppSelector hook.
|
||||
const renderModal = (
|
||||
props: Partial<ComponentProps<typeof SaveDatasetModal>> = {},
|
||||
) =>
|
||||
render(<SaveDatasetModal {...mockedProps} {...props} />, {
|
||||
useRedux: true,
|
||||
initialState: { user },
|
||||
});
|
||||
|
||||
fetchMock.get('glob:*/api/v1/dataset/?*', {
|
||||
result: mockdatasets,
|
||||
dataset_count: 3,
|
||||
@@ -47,17 +59,17 @@ fetchMock.get('glob:*/api/v1/dataset/?*', {
|
||||
|
||||
jest.useFakeTimers({ advanceTimers: true });
|
||||
|
||||
// Mock the user
|
||||
const useSelectorMock = jest.spyOn(reactRedux, 'useSelector');
|
||||
beforeEach(() => {
|
||||
useSelectorMock.mockClear();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
// Mock the createDatasource action
|
||||
const useDispatchMock = jest.spyOn(reactRedux, 'useDispatch');
|
||||
// Mock createDatasource to return a thunk that resolves with the dataset's
|
||||
// new id. The test's mock store includes redux-thunk middleware (from RTK's
|
||||
// getDefaultMiddleware), so dispatch(createDatasource(...)) properly unwraps
|
||||
// the thunk and the production code's .then((data) => clearDatasetCache(data.id))
|
||||
// chain receives `{ id: 123 }`. Individual tests can override per-call as needed.
|
||||
jest.mock('src/SqlLab/actions/sqlLab', () => ({
|
||||
createDatasource: jest.fn(),
|
||||
createDatasource: jest.fn(() => () => Promise.resolve({ id: 123 })),
|
||||
}));
|
||||
jest.mock('src/explore/exploreUtils/formData', () => ({
|
||||
postFormData: jest.fn(),
|
||||
@@ -70,7 +82,7 @@ jest.mock('src/utils/cachedSupersetGet', () => ({
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('SaveDatasetModal', () => {
|
||||
test('renders a "Save as new" field', () => {
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
renderModal();
|
||||
|
||||
const saveRadioBtn = screen.getByRole('radio', {
|
||||
name: /save as new/i,
|
||||
@@ -87,7 +99,7 @@ describe('SaveDatasetModal', () => {
|
||||
});
|
||||
|
||||
test('renders an "Overwrite existing" field', () => {
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
renderModal();
|
||||
|
||||
const overwriteRadioBtn = screen.getByRole('radio', {
|
||||
name: /overwrite existing/i,
|
||||
@@ -103,20 +115,20 @@ describe('SaveDatasetModal', () => {
|
||||
});
|
||||
|
||||
test('renders a close button', () => {
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
renderModal();
|
||||
|
||||
expect(screen.getByRole('button', { name: /close/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders a save button when "Save as new" is selected', () => {
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
renderModal();
|
||||
|
||||
// "Save as new" is selected when the modal opens by default
|
||||
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders an overwrite button when "Overwrite existing" is selected', () => {
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
renderModal();
|
||||
|
||||
// Click the overwrite radio button to reveal the overwrite confirmation and back buttons
|
||||
const overwriteRadioBtn = screen.getByRole('radio', {
|
||||
@@ -130,8 +142,7 @@ describe('SaveDatasetModal', () => {
|
||||
});
|
||||
|
||||
test('renders the overwrite button as disabled until an existing dataset is selected', async () => {
|
||||
useSelectorMock.mockReturnValue({ ...user });
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
renderModal();
|
||||
|
||||
// Click the overwrite radio button
|
||||
const overwriteRadioBtn = screen.getByRole('radio', {
|
||||
@@ -168,8 +179,7 @@ describe('SaveDatasetModal', () => {
|
||||
});
|
||||
|
||||
test('renders a confirm overwrite screen when overwrite is clicked', async () => {
|
||||
useSelectorMock.mockReturnValue({ ...user });
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
renderModal();
|
||||
|
||||
// Click the overwrite radio button
|
||||
const overwriteRadioBtn = screen.getByRole('radio', {
|
||||
@@ -215,11 +225,7 @@ describe('SaveDatasetModal', () => {
|
||||
});
|
||||
|
||||
test('sends the schema when creating the dataset', async () => {
|
||||
const dummyDispatch = jest.fn().mockResolvedValue({});
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
useSelectorMock.mockReturnValue({ ...user });
|
||||
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
renderModal();
|
||||
|
||||
const inputFieldText = screen.getByDisplayValue(/unimportant/i);
|
||||
fireEvent.change(inputFieldText, { target: { value: 'my dataset' } });
|
||||
@@ -240,17 +246,9 @@ describe('SaveDatasetModal', () => {
|
||||
});
|
||||
|
||||
test('sends the catalog when creating the dataset', async () => {
|
||||
const dummyDispatch = jest.fn().mockResolvedValue({});
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
useSelectorMock.mockReturnValue({ ...user });
|
||||
|
||||
render(
|
||||
<SaveDatasetModal
|
||||
{...mockedProps}
|
||||
datasource={{ ...mockedProps.datasource, catalog: 'public' }}
|
||||
/>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
renderModal({
|
||||
datasource: { ...mockedProps.datasource, catalog: 'public' },
|
||||
});
|
||||
|
||||
const inputFieldText = screen.getByDisplayValue(/unimportant/i);
|
||||
fireEvent.change(inputFieldText, { target: { value: 'my dataset' } });
|
||||
@@ -271,7 +269,7 @@ describe('SaveDatasetModal', () => {
|
||||
});
|
||||
|
||||
test('does not renders a checkbox button when template processing is disabled', () => {
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
renderModal();
|
||||
expect(screen.queryByRole('checkbox')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -280,7 +278,7 @@ describe('SaveDatasetModal', () => {
|
||||
global.featureFlags = {
|
||||
[FeatureFlag.EnableTemplateProcessing]: true,
|
||||
};
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
renderModal();
|
||||
expect(screen.getByRole('checkbox')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -289,15 +287,11 @@ describe('SaveDatasetModal', () => {
|
||||
global.featureFlags = {
|
||||
[FeatureFlag.EnableTemplateProcessing]: true,
|
||||
};
|
||||
const propsWithTemplateParam = {
|
||||
...mockedProps,
|
||||
renderModal({
|
||||
datasource: {
|
||||
...testQuery,
|
||||
templateParams: JSON.stringify({ my_param: 12 }),
|
||||
},
|
||||
};
|
||||
render(<SaveDatasetModal {...propsWithTemplateParam} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
const inputFieldText = screen.getByDisplayValue(/unimportant/i);
|
||||
fireEvent.change(inputFieldText, { target: { value: 'my dataset' } });
|
||||
@@ -324,15 +318,11 @@ describe('SaveDatasetModal', () => {
|
||||
global.featureFlags = {
|
||||
[FeatureFlag.EnableTemplateProcessing]: true,
|
||||
};
|
||||
const propsWithTemplateParam = {
|
||||
...mockedProps,
|
||||
renderModal({
|
||||
datasource: {
|
||||
...testQuery,
|
||||
templateParams: JSON.stringify({ my_param: 12 }),
|
||||
},
|
||||
};
|
||||
render(<SaveDatasetModal {...propsWithTemplateParam} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
const inputFieldText = screen.getByDisplayValue(/unimportant/i);
|
||||
fireEvent.change(inputFieldText, { target: { value: 'my dataset' } });
|
||||
@@ -393,19 +383,11 @@ describe('SaveDatasetModal', () => {
|
||||
.spyOn(SupersetClient, 'put')
|
||||
.mockResolvedValue({ json: { result: { id: 0 } } } as any);
|
||||
|
||||
const dummyDispatch = jest.fn().mockResolvedValue({});
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
useSelectorMock.mockReturnValue({ ...user });
|
||||
|
||||
const propsWithTemplateParam = {
|
||||
...mockedProps,
|
||||
renderModal({
|
||||
datasource: {
|
||||
...testQuery,
|
||||
templateParams: JSON.stringify({ my_param: 12, _filters: 'foo' }),
|
||||
},
|
||||
};
|
||||
render(<SaveDatasetModal {...propsWithTemplateParam} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
// Check the "Include Template Parameters" checkbox
|
||||
@@ -443,19 +425,11 @@ describe('SaveDatasetModal', () => {
|
||||
.spyOn(SupersetClient, 'put')
|
||||
.mockResolvedValue({ json: { result: { id: 0 } } } as any);
|
||||
|
||||
const dummyDispatch = jest.fn().mockResolvedValue({});
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
useSelectorMock.mockReturnValue({ ...user });
|
||||
|
||||
const propsWithTemplateParam = {
|
||||
...mockedProps,
|
||||
renderModal({
|
||||
datasource: {
|
||||
...testQuery,
|
||||
templateParams: JSON.stringify({ my_param: 12 }),
|
||||
},
|
||||
};
|
||||
render(<SaveDatasetModal {...propsWithTemplateParam} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
// Do NOT check the "Include Template Parameters" checkbox
|
||||
@@ -489,12 +463,9 @@ describe('SaveDatasetModal', () => {
|
||||
'postFormData',
|
||||
);
|
||||
|
||||
const dummyDispatch = jest.fn().mockResolvedValue({ id: 123 });
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
useSelectorMock.mockReturnValue({ ...user });
|
||||
postFormData.mockResolvedValue('chart_key_123');
|
||||
|
||||
render(<SaveDatasetModal {...mockedProps} />, { useRedux: true });
|
||||
renderModal();
|
||||
|
||||
const inputFieldText = screen.getByDisplayValue(/unimportant/i);
|
||||
fireEvent.change(inputFieldText, { target: { value: 'my dataset' } });
|
||||
|
||||
@@ -34,7 +34,6 @@ import { t } from '@apache-superset/core/translation';
|
||||
import {
|
||||
SupersetClient,
|
||||
JsonResponse,
|
||||
JsonObject,
|
||||
QueryResponse,
|
||||
QueryFormData,
|
||||
VizType,
|
||||
@@ -44,16 +43,14 @@ import {
|
||||
} from '@superset-ui/core';
|
||||
import { styled } from '@apache-superset/core/theme';
|
||||
import { extendedDayjs as dayjs } from '@superset-ui/core/utils/dates';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { useAppDispatch, useAppSelector } from 'src/views/store';
|
||||
import rison from 'rison';
|
||||
import { createDatasource } from 'src/SqlLab/actions/sqlLab';
|
||||
import { addDangerToast } from 'src/components/MessageToasts/actions';
|
||||
import { UserWithPermissionsAndRoles as User } from 'src/types/bootstrapTypes';
|
||||
import {
|
||||
DatasetRadioState,
|
||||
EXPLORE_CHART_DEFAULT,
|
||||
DatasetOwner,
|
||||
SqlLabRootState,
|
||||
} from 'src/SqlLab/types';
|
||||
import { mountExploreUrl } from 'src/explore/exploreUtils';
|
||||
import { postFormData } from 'src/explore/exploreUtils/formData';
|
||||
@@ -221,7 +218,7 @@ export const SaveDatasetModal = ({
|
||||
openWindow = true,
|
||||
formData = {},
|
||||
}: SaveDatasetModalProps) => {
|
||||
const defaultVizType = useSelector<SqlLabRootState, string>(
|
||||
const defaultVizType = useAppSelector(
|
||||
state => state.common?.conf?.DEFAULT_VIZ_TYPE || VizType.Table,
|
||||
);
|
||||
|
||||
@@ -240,8 +237,8 @@ export const SaveDatasetModal = ({
|
||||
>(undefined);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const user = useSelector<SqlLabRootState, User>(state => state.user);
|
||||
const dispatch = useDispatch<(dispatch: any) => Promise<JsonObject>>();
|
||||
const user = useAppSelector(state => state.user);
|
||||
const dispatch = useAppDispatch();
|
||||
const [includeTemplateParameters, setIncludeTemplateParameters] =
|
||||
useState(false);
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { createRef, useCallback, useMemo } from 'react';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import { nanoid } from 'nanoid';
|
||||
import Tabs from '@superset-ui/core/components/Tabs';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
@@ -105,7 +106,7 @@ const SouthPane = ({
|
||||
const { id, tabViewId } = useQueryEditor(queryEditorId, ['tabViewId']);
|
||||
const editorId = tabViewId ?? id;
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const viewItems = views.getViews(ViewLocations.sqllab.panels) || [];
|
||||
const { offline, tables } = useSelector(
|
||||
({ sqlLab: { offline, tables } }: SqlLabRootState) => ({
|
||||
|
||||
@@ -30,7 +30,8 @@ import {
|
||||
|
||||
import type { editors } from '@apache-superset/core';
|
||||
import useEffectEvent from 'src/hooks/useEffectEvent';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import {
|
||||
@@ -237,7 +238,7 @@ const SqlEditor: FC<Props> = ({
|
||||
scheduleQueryWarning,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { database, latestQuery, currentQueryEditorId, hasSqlStatement } =
|
||||
useSelector<
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
|
||||
import { resetState } from 'src/SqlLab/actions/sqlLab';
|
||||
import {
|
||||
@@ -69,7 +69,7 @@ const SqlEditorLeftBar = ({ queryEditorId }: SqlEditorLeftBarProps) => {
|
||||
const { db, catalog, schema, onDbChange, onCatalogChange, onSchemaChange } =
|
||||
dbSelectorProps;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const shouldShowReset = window.location.search === '?reset=1';
|
||||
|
||||
// Modal state for Database/Catalog/Schema selector
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
import { useMemo, FC } from 'react';
|
||||
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
|
||||
import { useSelector, shallowEqual } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import { MenuDotsDropdown } from '@superset-ui/core/components';
|
||||
import { Menu, MenuItemType } from '@superset-ui/core/components/Menu';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
@@ -90,7 +91,7 @@ const SqlEditorTabHeader: FC<Props> = ({ queryEditor }) => {
|
||||
);
|
||||
const StatusIcon = queryState ? STATE_ICONS[queryState] : STATE_ICONS.running;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const actions = useMemo(
|
||||
() =>
|
||||
bindActionCreators(
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useEffect, useCallback, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
|
||||
import { SqlLabRootState } from 'src/SqlLab/types';
|
||||
import {
|
||||
@@ -41,7 +42,7 @@ export default function useDatabaseSelector(queryEditorId: string) {
|
||||
SqlLabRootState,
|
||||
SqlLabRootState['sqlLab']['databases']
|
||||
>(({ sqlLab }) => sqlLab.databases);
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const queryEditor = useQueryEditor(queryEditorId, [
|
||||
'dbId',
|
||||
'catalog',
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import type { QueryEditor, SqlLabRootState, Table } from 'src/SqlLab/types';
|
||||
import {
|
||||
ButtonGroup,
|
||||
@@ -75,7 +76,7 @@ const Fade = styled.div`
|
||||
const TableElement = ({ table, ...props }: TableElementProps) => {
|
||||
const { dbId, catalog, schema, name, expanded, id } = table;
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const {
|
||||
currentData: tableMetadata,
|
||||
isSuccess: isMetadataSuccess,
|
||||
|
||||
@@ -25,7 +25,8 @@ import {
|
||||
type ChangeEvent,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
|
||||
import { useSelector, shallowEqual } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import { styled, css, useTheme } from '@apache-superset/core/theme';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
@@ -163,7 +164,7 @@ const savePinnedSchemasToStorage = (
|
||||
};
|
||||
|
||||
const TableExploreTree: React.FC<Props> = ({ queryEditorId }) => {
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const theme = useTheme();
|
||||
const treeRef = useRef<TreeApi<TreeNodeData>>(null);
|
||||
const tables = useSelector(
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { useMemo, useReducer, useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import {
|
||||
Table,
|
||||
@@ -130,7 +130,7 @@ const useTreeData = ({
|
||||
catalog,
|
||||
pinnedTables,
|
||||
}: UseTreeDataParams): UseTreeDataResult => {
|
||||
const reduxDispatch = useDispatch();
|
||||
const reduxDispatch = useAppDispatch();
|
||||
// Schema data from API
|
||||
const {
|
||||
currentData: schemaData,
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { type FC, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { useAppDispatch } from 'src/views/store';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { ClientErrorObject, getExtensionsRegistry } from '@superset-ui/core';
|
||||
@@ -110,7 +111,7 @@ const renderWell = (partitions: TableMetaData['partitions']) => {
|
||||
};
|
||||
|
||||
const TablePreview: FC<Props> = ({ dbId, catalog, schema, tableName }) => {
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useAppDispatch();
|
||||
const theme = useTheme();
|
||||
const [databaseName, backend, disableDataPreview] = useSelector<
|
||||
SqlLabRootState,
|
||||
|
||||
@@ -22,12 +22,13 @@ import {
|
||||
createListenerMiddleware,
|
||||
StoreEnhancer,
|
||||
} from '@reduxjs/toolkit';
|
||||
import type { AnyAction } from 'redux';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
type TypedUseSelectorHook,
|
||||
} from 'react-redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import thunk, { type ThunkDispatch } from 'redux-thunk';
|
||||
import { api } from 'src/hooks/apiResources/queryApi';
|
||||
import messageToastReducer from 'src/components/MessageToasts/reducers';
|
||||
import charts from 'src/components/Chart/chartReducer';
|
||||
@@ -188,6 +189,14 @@ export type RootState = ReturnType<typeof store.getState>;
|
||||
// thunks resolve correctly), and `useAppSelector` infers `RootState` without
|
||||
// callers having to annotate every selector. Required ahead of the
|
||||
// react-redux v8+ bump, which tightens dispatch typing — see #39927.
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
//
|
||||
// AppDispatch is declared as ThunkDispatch & store.dispatch rather than
|
||||
// `typeof store.dispatch` because Superset annotates getMiddleware as
|
||||
// ConfigureStoreOptions['middleware'], which erases the middleware tuple type
|
||||
// and leaves store.dispatch typed as Dispatch<AnyAction>. The intersection
|
||||
// restores thunk support without requiring a wider refactor of the middleware
|
||||
// setup.
|
||||
export type AppDispatch = ThunkDispatch<RootState, undefined, AnyAction> &
|
||||
typeof store.dispatch;
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch;
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||
|
||||
@@ -152,7 +152,7 @@ class AsyncQueryManager:
|
||||
self.register_request_handlers(app)
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.tasks.async_queries import ( # noqa: PLC0415
|
||||
from superset.tasks.async_queries import (
|
||||
load_chart_data_into_cache,
|
||||
load_explore_json_into_cache,
|
||||
)
|
||||
@@ -222,7 +222,7 @@ class AsyncQueryManager:
|
||||
user_id: Optional[int] = None,
|
||||
) -> dict[str, Any]:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
job_metadata = self.init_job(channel_id, user_id)
|
||||
self._load_explore_json_into_cache_job.delay(
|
||||
@@ -242,7 +242,7 @@ class AsyncQueryManager:
|
||||
user_id: Optional[int] = None,
|
||||
) -> dict[str, Any]:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
# if it's guest user, we want to pass the guest token to the celery task
|
||||
# chart data cache key is calculated based on the current user
|
||||
|
||||
@@ -1433,9 +1433,7 @@ class ChartDataQueryContextSchema(Schema):
|
||||
def get_query_context_factory(self) -> QueryContextFactory:
|
||||
if self.query_context_factory is None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.common.query_context_factory import ( # noqa: PLC0415
|
||||
QueryContextFactory,
|
||||
)
|
||||
from superset.common.query_context_factory import QueryContextFactory
|
||||
|
||||
self.query_context_factory = QueryContextFactory()
|
||||
return self.query_context_factory
|
||||
|
||||
@@ -219,7 +219,7 @@ class ExportDashboardsCommand(ExportModelsCommand):
|
||||
|
||||
# Export related theme
|
||||
if model.theme:
|
||||
from superset.commands.theme.export import ExportThemesCommand # noqa: PLC0415
|
||||
from superset.commands.theme.export import ExportThemesCommand
|
||||
|
||||
yield from ExportThemesCommand([model.theme.id]).run()
|
||||
|
||||
|
||||
@@ -235,9 +235,9 @@ def export_dataset_data(
|
||||
sample_rows: int | None = None,
|
||||
) -> bytes | None:
|
||||
"""Export dataset data to Parquet format. Returns bytes or None on failure."""
|
||||
import pandas as pd # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
import pandas as pd # pylint: disable=import-outside-toplevel
|
||||
|
||||
from superset import db # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset import db # pylint: disable=import-outside-toplevel
|
||||
|
||||
# Ensure dataset is attached to session and relationships are loaded
|
||||
if dataset not in db.session:
|
||||
@@ -394,7 +394,7 @@ def export_dashboard_yaml(
|
||||
dataset_id_to_uuid: dict[int, str],
|
||||
) -> dict[str, Any]:
|
||||
"""Export dashboard to YAML format with proper ID remapping."""
|
||||
from superset.utils import ( # noqa: PLC0415
|
||||
from superset.utils import (
|
||||
json as superset_json, # pylint: disable=import-outside-toplevel
|
||||
)
|
||||
|
||||
|
||||
@@ -62,6 +62,21 @@ def build_uuid_to_id_map(position: dict[str, Any]) -> dict[str, int]:
|
||||
}
|
||||
|
||||
|
||||
def _remap_charts_in_scope(container: dict[str, Any], id_map: dict[int, int]) -> None:
|
||||
"""Remap source-env chart IDs in ``container["chartsInScope"]`` in place.
|
||||
|
||||
``chartsInScope`` is a denormalized cache of the charts a filter (native
|
||||
or cross-filter) currently applies to. Both surfaces share this contract,
|
||||
so they share this remap. Unresolvable IDs are dropped rather than
|
||||
passed through, matching the convention used for ``scope.excluded``.
|
||||
"""
|
||||
charts_in_scope = container.get("chartsInScope")
|
||||
if isinstance(charts_in_scope, list):
|
||||
container["chartsInScope"] = [
|
||||
id_map[old_id] for old_id in charts_in_scope if old_id in id_map
|
||||
]
|
||||
|
||||
|
||||
def update_id_refs( # pylint: disable=too-many-locals # noqa: C901
|
||||
config: dict[str, Any],
|
||||
chart_ids: dict[str, int],
|
||||
@@ -145,6 +160,8 @@ def update_id_refs( # pylint: disable=too-many-locals # noqa: C901
|
||||
id_map[old_id] for old_id in scope_excluded if old_id in id_map
|
||||
]
|
||||
|
||||
_remap_charts_in_scope(native_filter, id_map)
|
||||
|
||||
# fix display control dataset references
|
||||
for customization in (
|
||||
fixed.get("metadata", {}).get("chart_customization_config") or []
|
||||
@@ -170,7 +187,7 @@ def update_id_refs( # pylint: disable=too-many-locals # noqa: C901
|
||||
return fixed
|
||||
|
||||
|
||||
def update_cross_filter_scoping(
|
||||
def update_cross_filter_scoping( # noqa: C901
|
||||
config: dict[str, Any], id_map: dict[int, int]
|
||||
) -> dict[str, Any]:
|
||||
# fix cross filter references
|
||||
@@ -185,6 +202,9 @@ def update_cross_filter_scoping(
|
||||
id_map[old_id] for old_id in scope_excluded if old_id in id_map
|
||||
]
|
||||
|
||||
# Global cross-filter chartsInScope mirrors the native-filter case.
|
||||
_remap_charts_in_scope(cross_filter_global_config, id_map)
|
||||
|
||||
if "chart_configuration" in (metadata := fixed.get("metadata", {})):
|
||||
# Build remapped configuration in a single pass for clarity/readability.
|
||||
new_chart_configuration: dict[str, Any] = {}
|
||||
@@ -212,6 +232,11 @@ def update_cross_filter_scoping(
|
||||
if old_id in id_map
|
||||
]
|
||||
|
||||
# Cross-filter chartsInScope mirrors the native-filter case.
|
||||
cross_filters = chart_config.get("crossFilters")
|
||||
if isinstance(cross_filters, dict):
|
||||
_remap_charts_in_scope(cross_filters, id_map)
|
||||
|
||||
new_chart_configuration[str(new_id)] = chart_config
|
||||
|
||||
metadata["chart_configuration"] = new_chart_configuration
|
||||
|
||||
@@ -158,13 +158,8 @@ class UpdateDatabaseCommand(BaseCommand):
|
||||
"""
|
||||
Update the catalog of the datasets that are associated with database.
|
||||
"""
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: PLC0415
|
||||
from superset.models.sql_lab import ( # noqa: PLC0415
|
||||
Query,
|
||||
SavedQuery,
|
||||
TableSchema,
|
||||
TabState,
|
||||
)
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.models.sql_lab import Query, SavedQuery, TableSchema, TabState
|
||||
|
||||
for model in [
|
||||
SqlaTable,
|
||||
|
||||
@@ -101,7 +101,7 @@ class CSVReader(BaseDataReader):
|
||||
return "c"
|
||||
|
||||
# Import pyarrow to verify it works properly
|
||||
import pyarrow as pa # noqa: F401, PLC0415
|
||||
import pyarrow as pa # noqa: F401
|
||||
|
||||
# Check if pandas has built-in pyarrow support
|
||||
pandas_version = str(pd.__version__)
|
||||
|
||||
@@ -209,7 +209,7 @@ def load_data(data_uri: str, dataset: SqlaTable, database: Database) -> None:
|
||||
:raises DatasetUnAllowedDataURI: If a dataset is trying
|
||||
to load data from a URI that is not allowed.
|
||||
"""
|
||||
from superset.examples.helpers import normalize_example_data_url # noqa: PLC0415
|
||||
from superset.examples.helpers import normalize_example_data_url
|
||||
|
||||
# Convert example URLs to align with configuration
|
||||
data_uri = normalize_example_data_url(data_uri)
|
||||
|
||||
@@ -216,9 +216,7 @@ def load_configs(
|
||||
|
||||
# Normalize example data URLs before schema validation
|
||||
if prefix == "datasets" and "data" in config:
|
||||
from superset.examples.helpers import ( # noqa: PLC0415
|
||||
normalize_example_data_url,
|
||||
)
|
||||
from superset.examples.helpers import normalize_example_data_url
|
||||
|
||||
config["data"] = normalize_example_data_url(config["data"])
|
||||
|
||||
@@ -355,7 +353,7 @@ def safe_insert_dashboard_chart_relationships(
|
||||
This function checks for existing relationships and only inserts new ones
|
||||
to avoid duplicate key constraint errors.
|
||||
"""
|
||||
from sqlalchemy.sql import select # noqa: PLC0415
|
||||
from sqlalchemy.sql import select
|
||||
|
||||
if not dashboard_chart_ids:
|
||||
return
|
||||
|
||||
@@ -179,7 +179,7 @@ class BaseReportState:
|
||||
"""
|
||||
Creates a Report execution log, uses the current computed last_value for Alerts
|
||||
"""
|
||||
from sqlalchemy.orm.exc import StaleDataError # noqa: PLC0415
|
||||
from sqlalchemy.orm.exc import StaleDataError
|
||||
|
||||
try:
|
||||
log = ReportExecutionLog(
|
||||
|
||||
@@ -233,7 +233,7 @@ class BaseStreamingCSVExportCommand(BaseCommand):
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Error in streaming CSV generator: %s", e)
|
||||
import traceback # noqa: PLC0415
|
||||
import traceback
|
||||
|
||||
logger.error("Traceback: %s", traceback.format_exc())
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ class CancelTaskCommand(BaseCommand):
|
||||
|
||||
:returns: The updated task model
|
||||
"""
|
||||
from superset.daos.tasks import TaskDAO # noqa: PLC0415
|
||||
from superset.daos.tasks import TaskDAO
|
||||
|
||||
# Lightweight fetch to compute dedup_key for locking
|
||||
# This is needed to use the same lock key as SubmitTaskCommand
|
||||
@@ -113,7 +113,7 @@ class CancelTaskCommand(BaseCommand):
|
||||
# Publish abort notification AFTER transaction commits
|
||||
# This prevents race conditions where listeners check DB before commit
|
||||
if self._should_publish_abort:
|
||||
from superset.tasks.manager import TaskManager # noqa: PLC0415
|
||||
from superset.tasks.manager import TaskManager
|
||||
|
||||
TaskManager.publish_abort(self._task_uuid)
|
||||
|
||||
@@ -129,7 +129,7 @@ class CancelTaskCommand(BaseCommand):
|
||||
|
||||
:returns: The updated task model
|
||||
"""
|
||||
from superset.daos.tasks import TaskDAO # noqa: PLC0415
|
||||
from superset.daos.tasks import TaskDAO
|
||||
|
||||
# Check admin status (no DB access)
|
||||
is_admin = security_manager.is_admin()
|
||||
@@ -232,7 +232,7 @@ class CancelTaskCommand(BaseCommand):
|
||||
:param is_admin: Whether current user is admin
|
||||
:returns: The updated task model
|
||||
"""
|
||||
from superset.daos.tasks import TaskDAO # noqa: PLC0415
|
||||
from superset.daos.tasks import TaskDAO
|
||||
|
||||
try:
|
||||
result: Task | None = TaskDAO.abort_task(
|
||||
@@ -273,7 +273,7 @@ class CancelTaskCommand(BaseCommand):
|
||||
:param user_id: ID of user to unsubscribe
|
||||
:returns: The updated task model
|
||||
"""
|
||||
from superset.daos.tasks import TaskDAO # noqa: PLC0415
|
||||
from superset.daos.tasks import TaskDAO
|
||||
|
||||
self._action_taken = "unsubscribed"
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class TaskPruneCommand(BaseCommand):
|
||||
|
||||
# Select all IDs that need to be deleted
|
||||
# Only delete completed tasks (success, failure, or aborted)
|
||||
from superset.models.tasks import Task # noqa: PLC0415
|
||||
from superset.models.tasks import Task
|
||||
|
||||
select_stmt = sa.select(Task.id).where(
|
||||
Task.ended_at < datetime.now() - timedelta(days=self.retention_period_days),
|
||||
|
||||
@@ -80,7 +80,7 @@ class SubmitTaskCommand(BaseCommand):
|
||||
|
||||
:returns: Tuple of (Task, is_new) where is_new is True if task was created
|
||||
"""
|
||||
from superset.daos.tasks import TaskDAO # noqa: PLC0415
|
||||
from superset.daos.tasks import TaskDAO
|
||||
|
||||
self.validate()
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ class UpdateTaskCommand(BaseCommand):
|
||||
|
||||
:returns: The updated task model
|
||||
"""
|
||||
from superset.daos.tasks import TaskDAO # noqa: PLC0415
|
||||
from superset.daos.tasks import TaskDAO
|
||||
|
||||
self.validate()
|
||||
|
||||
@@ -129,7 +129,7 @@ class UpdateTaskCommand(BaseCommand):
|
||||
|
||||
:returns: The updated task model
|
||||
"""
|
||||
from superset.daos.tasks import TaskDAO # noqa: PLC0415
|
||||
from superset.daos.tasks import TaskDAO
|
||||
|
||||
# Re-fetch model under lock to get fresh state
|
||||
fresh_model = TaskDAO.find_one_or_none(
|
||||
|
||||
@@ -68,7 +68,7 @@ class DeleteThemeCommand(BaseCommand):
|
||||
|
||||
def _dissociate_dashboards(self) -> None:
|
||||
"""Dissociate dashboards from themes before deletion."""
|
||||
from superset.models.dashboard import Dashboard # noqa: PLC0415
|
||||
from superset.models.dashboard import Dashboard
|
||||
|
||||
theme_ids = [theme.id for theme in self._models or []]
|
||||
if not theme_ids:
|
||||
@@ -95,7 +95,7 @@ class DeleteThemeCommand(BaseCommand):
|
||||
|
||||
def _get_dashboard_usage(self) -> dict[int, list[str]]:
|
||||
"""Get dashboard names that use these themes."""
|
||||
from superset.models.dashboard import Dashboard # noqa: PLC0415
|
||||
from superset.models.dashboard import Dashboard
|
||||
|
||||
theme_ids = [theme.id for theme in self._models or []]
|
||||
if not theme_ids:
|
||||
|
||||
@@ -34,9 +34,9 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def import_theme(config: dict[str, Any], overwrite: bool = False) -> "Theme | None":
|
||||
"""Import a single theme from config dictionary"""
|
||||
from superset import db, security_manager # noqa: PLC0415
|
||||
from superset.models.core import Theme # noqa: PLC0415
|
||||
from superset.utils.core import get_user # noqa: PLC0415
|
||||
from superset import db, security_manager
|
||||
from superset.models.core import Theme
|
||||
from superset.utils.core import get_user
|
||||
|
||||
can_write = security_manager.can_access("can_write", "Theme")
|
||||
existing = db.session.query(Theme).filter_by(uuid=config["uuid"]).first()
|
||||
|
||||
@@ -457,9 +457,7 @@ class QueryContextProcessor:
|
||||
annotation_layer: dict[str, Any], force: bool
|
||||
) -> dict[str, Any]:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.commands.chart.data.get_data_command import ( # noqa: PLC0415
|
||||
ChartDataCommand,
|
||||
)
|
||||
from superset.commands.chart.data.get_data_command import ChartDataCommand
|
||||
|
||||
if not (chart := ChartDAO.find_by_id(annotation_layer["value"])):
|
||||
raise QueryObjectValidationError(
|
||||
|
||||
@@ -335,7 +335,7 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
|
||||
return False
|
||||
|
||||
def _sanitize_filters(self) -> None:
|
||||
from superset.jinja_context import get_template_processor # noqa: PLC0415
|
||||
from superset.jinja_context import get_template_processor
|
||||
|
||||
needs_transpilation = self.extras.get("transpile_to_dialect", False)
|
||||
|
||||
|
||||
@@ -2053,7 +2053,7 @@ class SqlaTable(
|
||||
:param query_obj: query object to analyze
|
||||
:return: The extra cache keys
|
||||
"""
|
||||
from superset.utils.rls import collect_rls_predicates_for_sql # noqa: PLC0415
|
||||
from superset.utils.rls import collect_rls_predicates_for_sql
|
||||
|
||||
extra_cache_keys = super().get_extra_cache_keys(query_obj)
|
||||
if self.has_extra_cache_key_calls(query_obj):
|
||||
|
||||
@@ -42,24 +42,22 @@ def inject_dao_implementations() -> None:
|
||||
Replace abstract DAO classes in superset_core common/queries/tasks daos with
|
||||
concrete implementations from Superset.
|
||||
"""
|
||||
import superset_core.common.daos as core_common_dao_module # noqa: PLC0415
|
||||
import superset_core.queries.daos as core_queries_dao_module # noqa: PLC0415
|
||||
import superset_core.tasks.daos as core_tasks_dao_module # noqa: PLC0415
|
||||
import superset_core.common.daos as core_common_dao_module
|
||||
import superset_core.queries.daos as core_queries_dao_module
|
||||
import superset_core.tasks.daos as core_tasks_dao_module
|
||||
|
||||
from superset.daos.chart import ChartDAO as HostChartDAO # noqa: PLC0415
|
||||
from superset.daos.dashboard import ( # noqa: PLC0415
|
||||
DashboardDAO as HostDashboardDAO,
|
||||
)
|
||||
from superset.daos.database import DatabaseDAO as HostDatabaseDAO # noqa: PLC0415
|
||||
from superset.daos.dataset import DatasetDAO as HostDatasetDAO # noqa: PLC0415
|
||||
from superset.daos.key_value import KeyValueDAO as HostKeyValueDAO # noqa: PLC0415
|
||||
from superset.daos.query import ( # noqa: PLC0415
|
||||
from superset.daos.chart import ChartDAO as HostChartDAO
|
||||
from superset.daos.dashboard import DashboardDAO as HostDashboardDAO
|
||||
from superset.daos.database import DatabaseDAO as HostDatabaseDAO
|
||||
from superset.daos.dataset import DatasetDAO as HostDatasetDAO
|
||||
from superset.daos.key_value import KeyValueDAO as HostKeyValueDAO
|
||||
from superset.daos.query import (
|
||||
QueryDAO as HostQueryDAO,
|
||||
SavedQueryDAO as HostSavedQueryDAO,
|
||||
)
|
||||
from superset.daos.tag import TagDAO as HostTagDAO # noqa: PLC0415
|
||||
from superset.daos.tasks import TaskDAO as HostTaskDAO # noqa: PLC0415
|
||||
from superset.daos.user import UserDAO as HostUserDAO # noqa: PLC0415
|
||||
from superset.daos.tag import TagDAO as HostTagDAO
|
||||
from superset.daos.tasks import TaskDAO as HostTaskDAO
|
||||
from superset.daos.user import UserDAO as HostUserDAO
|
||||
|
||||
# Replace abstract classes in common.daos with concrete implementations
|
||||
core_common_dao_module.DatasetDAO = HostDatasetDAO # type: ignore[assignment,misc]
|
||||
@@ -85,24 +83,19 @@ def inject_model_implementations() -> None:
|
||||
|
||||
Uses in-place replacement to maintain single import location for extensions.
|
||||
"""
|
||||
import superset_core.common.models as core_common_models_module # noqa: PLC0415
|
||||
import superset_core.queries.models as core_queries_models_module # noqa: PLC0415
|
||||
import superset_core.tasks.models as core_tasks_models_module # noqa: PLC0415
|
||||
from flask_appbuilder.security.sqla.models import User as HostUser # noqa: PLC0415
|
||||
import superset_core.common.models as core_common_models_module
|
||||
import superset_core.queries.models as core_queries_models_module
|
||||
import superset_core.tasks.models as core_tasks_models_module
|
||||
from flask_appbuilder.security.sqla.models import User as HostUser
|
||||
|
||||
from superset.connectors.sqla.models import ( # noqa: PLC0415
|
||||
SqlaTable as HostDataset,
|
||||
)
|
||||
from superset.key_value.models import KeyValueEntry as HostKeyValue # noqa: PLC0415
|
||||
from superset.models.core import Database as HostDatabase # noqa: PLC0415
|
||||
from superset.models.dashboard import Dashboard as HostDashboard # noqa: PLC0415
|
||||
from superset.models.slice import Slice as HostChart # noqa: PLC0415
|
||||
from superset.models.sql_lab import ( # noqa: PLC0415
|
||||
Query as HostQuery,
|
||||
SavedQuery as HostSavedQuery,
|
||||
)
|
||||
from superset.models.tasks import Task as HostTask # noqa: PLC0415
|
||||
from superset.tags.models import Tag as HostTag # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import SqlaTable as HostDataset
|
||||
from superset.key_value.models import KeyValueEntry as HostKeyValue
|
||||
from superset.models.core import Database as HostDatabase
|
||||
from superset.models.dashboard import Dashboard as HostDashboard
|
||||
from superset.models.slice import Slice as HostChart
|
||||
from superset.models.sql_lab import Query as HostQuery, SavedQuery as HostSavedQuery
|
||||
from superset.models.tasks import Task as HostTask
|
||||
from superset.tags.models import Tag as HostTag
|
||||
|
||||
# In-place replacement in common.models
|
||||
core_common_models_module.Database = HostDatabase # type: ignore[misc]
|
||||
@@ -126,9 +119,9 @@ def inject_query_implementations() -> None:
|
||||
Replace abstract query functions in superset_core.queries.query with concrete
|
||||
implementations from Superset.
|
||||
"""
|
||||
import superset_core.queries.query as core_query_module # noqa: PLC0415
|
||||
import superset_core.queries.query as core_query_module
|
||||
|
||||
from superset.sql.parse import SQLGLOT_DIALECTS # noqa: PLC0415
|
||||
from superset.sql.parse import SQLGLOT_DIALECTS
|
||||
|
||||
def get_sqlglot_dialect(database: "Database") -> Any:
|
||||
return (
|
||||
@@ -144,12 +137,12 @@ def inject_task_implementations() -> None:
|
||||
Replace abstract task functions in superset_core tasks.types and tasks.decorators
|
||||
with concrete implementations from Superset.
|
||||
"""
|
||||
import superset_core.tasks.decorators as core_tasks_decorators_module # noqa: PLC0415
|
||||
import superset_core.tasks.types as core_tasks_types_module # noqa: PLC0415
|
||||
import superset_core.tasks.decorators as core_tasks_decorators_module
|
||||
import superset_core.tasks.types as core_tasks_types_module
|
||||
|
||||
from superset.tasks.ambient_context import get_context # noqa: PLC0415
|
||||
from superset.tasks.context import TaskContext # noqa: PLC0415
|
||||
from superset.tasks.decorators import task # noqa: PLC0415
|
||||
from superset.tasks.ambient_context import get_context
|
||||
from superset.tasks.context import TaskContext
|
||||
from superset.tasks.decorators import task
|
||||
|
||||
# Replace abstract classes and functions with concrete implementations
|
||||
core_tasks_types_module.TaskContext = TaskContext # type: ignore[assignment,misc]
|
||||
@@ -162,9 +155,9 @@ def inject_rest_api_implementations() -> None:
|
||||
Replace abstract REST API decorators in superset_core.rest_api.decorators
|
||||
with concrete implementations from Superset.
|
||||
"""
|
||||
import superset_core.rest_api.decorators as core_rest_api_module # noqa: PLC0415
|
||||
import superset_core.rest_api.decorators as core_rest_api_module
|
||||
|
||||
from superset.extensions import appbuilder # noqa: PLC0415
|
||||
from superset.extensions import appbuilder
|
||||
|
||||
T = TypeVar("T", bound=type["RestApi"])
|
||||
|
||||
@@ -226,10 +219,10 @@ def inject_model_session_implementation() -> None:
|
||||
Replace abstract get_session function in superset_core.common.models with concrete
|
||||
implementation from Superset.
|
||||
"""
|
||||
import superset_core.common.models as core_models_module # noqa: PLC0415
|
||||
import superset_core.common.models as core_models_module
|
||||
|
||||
def get_session() -> scoped_session:
|
||||
from superset import db # noqa: PLC0415
|
||||
from superset import db
|
||||
|
||||
return db.session
|
||||
|
||||
@@ -242,10 +235,10 @@ def inject_semantic_layer_implementations() -> None:
|
||||
superset_core.semantic_layers.decorators with a concrete implementation
|
||||
that registers classes in the contributions registry.
|
||||
"""
|
||||
import superset_core.semantic_layers.decorators as core_sl_module # noqa: PLC0415
|
||||
import superset_core.semantic_layers.decorators as core_sl_module
|
||||
|
||||
import superset.extensions.context as context_module # noqa: PLC0415
|
||||
from superset.semantic_layers.registry import registry # noqa: PLC0415
|
||||
import superset.extensions.context as context_module
|
||||
from superset.semantic_layers.registry import registry
|
||||
|
||||
def semantic_layer_impl(
|
||||
id: str,
|
||||
|
||||
@@ -98,7 +98,7 @@ def create_tool_decorator(
|
||||
def decorator(func: F) -> F:
|
||||
try:
|
||||
# Import here to avoid circular imports
|
||||
from superset.mcp_service.app import mcp # noqa: PLC0415
|
||||
from superset.mcp_service.app import mcp
|
||||
|
||||
# Use provided values or extract from function
|
||||
base_tool_name = name or func.__name__
|
||||
@@ -111,7 +111,7 @@ def create_tool_decorator(
|
||||
# Store RBAC permission metadata on the function so
|
||||
# mcp_auth_hook can read them at call time.
|
||||
if class_permission_name:
|
||||
from superset.mcp_service.auth import ( # noqa: PLC0415
|
||||
from superset.mcp_service.auth import (
|
||||
CLASS_PERMISSION_ATTR,
|
||||
METHOD_PERMISSION_ATTR,
|
||||
)
|
||||
@@ -124,13 +124,13 @@ def create_tool_decorator(
|
||||
|
||||
# Conditionally apply authentication wrapper
|
||||
if protect:
|
||||
from superset.mcp_service.auth import mcp_auth_hook # noqa: PLC0415
|
||||
from superset.mcp_service.auth import mcp_auth_hook
|
||||
|
||||
wrapped_func = mcp_auth_hook(func)
|
||||
else:
|
||||
wrapped_func = func
|
||||
|
||||
from fastmcp.tools import Tool # noqa: PLC0415
|
||||
from fastmcp.tools import Tool
|
||||
|
||||
tool = Tool.from_function(
|
||||
wrapped_func,
|
||||
@@ -208,7 +208,7 @@ def create_prompt_decorator(
|
||||
def decorator(func: F) -> F:
|
||||
try:
|
||||
# Import here to avoid circular imports
|
||||
from superset.mcp_service.app import mcp # noqa: PLC0415
|
||||
from superset.mcp_service.app import mcp
|
||||
|
||||
# Use provided values or extract from function
|
||||
base_prompt_name = name or func.__name__
|
||||
@@ -223,7 +223,7 @@ def create_prompt_decorator(
|
||||
|
||||
# Conditionally apply authentication wrapper
|
||||
if protect:
|
||||
from superset.mcp_service.auth import mcp_auth_hook # noqa: PLC0415
|
||||
from superset.mcp_service.auth import mcp_auth_hook
|
||||
|
||||
wrapped_func = mcp_auth_hook(func)
|
||||
else:
|
||||
@@ -279,10 +279,10 @@ def initialize_core_mcp_dependencies() -> None:
|
||||
|
||||
Also imports MCP service app to register all host tools BEFORE extension loading.
|
||||
"""
|
||||
import superset_core.mcp.decorators # noqa: PLC0415
|
||||
import superset_core.mcp.decorators
|
||||
|
||||
try:
|
||||
from fastmcp.tools import Tool # noqa: F401, PLC0415
|
||||
from fastmcp.tools import Tool # noqa: F401
|
||||
except ImportError:
|
||||
logger.info(
|
||||
"fastmcp is not installed, skipping MCP initialization. "
|
||||
@@ -299,7 +299,7 @@ def initialize_core_mcp_dependencies() -> None:
|
||||
try:
|
||||
# Import MCP service app to register host tools BEFORE extension loading
|
||||
# This prevents host tools from being registered during extension context
|
||||
from superset.mcp_service import app # noqa: F401, PLC0415
|
||||
from superset.mcp_service import app # noqa: F401
|
||||
|
||||
logger.info("MCP service app imported - host tools registered")
|
||||
except Exception as e:
|
||||
|
||||
@@ -213,9 +213,7 @@ class TaskDAO(BaseDAO[Task]):
|
||||
:returns: Task if aborted/aborting, None if not found or already finished
|
||||
:raises TaskNotAbortableError: If in-progress task has no abort handler
|
||||
"""
|
||||
from superset.commands.tasks.exceptions import ( # noqa: PLC0415
|
||||
TaskNotAbortableError,
|
||||
)
|
||||
from superset.commands.tasks.exceptions import TaskNotAbortableError
|
||||
|
||||
task = cls.find_one_or_none(skip_base_filter=skip_base_filter, uuid=task_uuid)
|
||||
if not task:
|
||||
|
||||
@@ -749,9 +749,7 @@ class DatasetRestApi(BaseSupersetModelRestApi):
|
||||
$ref: '#/components/responses/500'
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.datasets.datetime_format_detector import ( # noqa: PLC0415
|
||||
DatetimeFormatDetector,
|
||||
)
|
||||
from superset.datasets.datetime_format_detector import DatetimeFormatDetector
|
||||
|
||||
try:
|
||||
# Get force parameter from query string
|
||||
|
||||
@@ -50,12 +50,8 @@ def DistributedLock( # noqa: N802
|
||||
or Redis connection fails
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.commands.distributed_lock.acquire import ( # noqa: PLC0415
|
||||
AcquireDistributedLock,
|
||||
)
|
||||
from superset.commands.distributed_lock.release import ( # noqa: PLC0415
|
||||
ReleaseDistributedLock,
|
||||
)
|
||||
from superset.commands.distributed_lock.acquire import AcquireDistributedLock
|
||||
from superset.commands.distributed_lock.release import ReleaseDistributedLock
|
||||
|
||||
key = get_key(namespace, **kwargs)
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ def get_dataset_config_from_yaml(example_dir: Path) -> Dict[str, Optional[str]]:
|
||||
|
||||
def get_examples_directory() -> Path:
|
||||
"""Get the path to the examples directory."""
|
||||
from .helpers import get_examples_folder # noqa: PLC0415
|
||||
from .helpers import get_examples_folder
|
||||
|
||||
return Path(get_examples_folder())
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ def load_parquet_table( # noqa: C901
|
||||
Returns:
|
||||
The created SqlaTable object
|
||||
"""
|
||||
from sqlalchemy import text # noqa: PLC0415
|
||||
from sqlalchemy import text
|
||||
|
||||
if database is None:
|
||||
database = get_example_database()
|
||||
|
||||
@@ -104,7 +104,7 @@ def normalize_example_data_url(url: str) -> str:
|
||||
Normalized file:// URL pointing to the Parquet file, or the original URL
|
||||
if it's a remote URL (http://, https://, etc.)
|
||||
"""
|
||||
import os # noqa: PLC0415
|
||||
import os
|
||||
|
||||
# Handle existing examples:// protocol
|
||||
if url.startswith(EXAMPLES_PROTOCOL):
|
||||
@@ -158,7 +158,7 @@ def read_example_data(
|
||||
Returns:
|
||||
DataFrame with the loaded data
|
||||
"""
|
||||
import os # noqa: PLC0415
|
||||
import os
|
||||
|
||||
# Extract example name from filepath
|
||||
if filepath.startswith(EXAMPLES_PROTOCOL):
|
||||
|
||||
@@ -50,7 +50,7 @@ def _read_file_if_exists(base: Any, path: Any) -> str | None:
|
||||
|
||||
def _load_shared_configs(examples_root: Any) -> dict[str, str]:
|
||||
"""Load shared database and metadata configs from _shared directory."""
|
||||
from flask import current_app # noqa: PLC0415
|
||||
from flask import current_app
|
||||
|
||||
contents: dict[str, str] = {}
|
||||
base = files("superset")
|
||||
|
||||
@@ -34,13 +34,13 @@ class ExtensionsRestApi(BaseApi):
|
||||
|
||||
def response(self, status_code: int, **kwargs: Any) -> Response:
|
||||
"""Helper method to create JSON responses."""
|
||||
from flask import jsonify # noqa: PLC0415
|
||||
from flask import jsonify
|
||||
|
||||
return jsonify(kwargs), status_code
|
||||
|
||||
def response_404(self) -> Response:
|
||||
"""Helper method to create 404 responses."""
|
||||
from flask import jsonify # noqa: PLC0415
|
||||
from flask import jsonify
|
||||
|
||||
return jsonify({"message": "Not found"}), 404
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ _watcher_lock = threading.Lock()
|
||||
def _get_file_handler_class() -> Any:
|
||||
"""Get the file handler class, importing watchdog only when needed."""
|
||||
try:
|
||||
from watchdog.events import FileSystemEventHandler # noqa: PLC0415
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
class LocalExtensionFileHandler(FileSystemEventHandler):
|
||||
"""Custom file system event handler for LOCAL_EXTENSIONS directories."""
|
||||
@@ -131,7 +131,7 @@ def setup_local_extensions_watcher(app: Flask) -> None: # noqa: C901
|
||||
return
|
||||
|
||||
try:
|
||||
from watchdog.observers import Observer # noqa: PLC0415
|
||||
from watchdog.observers import Observer
|
||||
|
||||
# Set up and start the file watcher
|
||||
event_handler = handler_class()
|
||||
|
||||
@@ -296,7 +296,7 @@ class SupersetShillelaghAdapter(Adapter):
|
||||
This is done on initialization because it's expensive.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.models.core import Database # noqa: PLC0415
|
||||
from superset.models.core import Database
|
||||
|
||||
database = (
|
||||
db.session.query(Database).filter_by(database_name=self.database).first()
|
||||
|
||||
@@ -79,7 +79,7 @@ class SupersetMetastoreCache(BaseCache):
|
||||
|
||||
def set(self, key: str, value: Any, timeout: Optional[int] = None) -> bool:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.daos.key_value import KeyValueDAO # noqa: PLC0415
|
||||
from superset.daos.key_value import KeyValueDAO
|
||||
|
||||
KeyValueDAO.upsert_entry(
|
||||
resource=RESOURCE,
|
||||
@@ -93,7 +93,7 @@ class SupersetMetastoreCache(BaseCache):
|
||||
|
||||
def add(self, key: str, value: Any, timeout: Optional[int] = None) -> bool:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.daos.key_value import KeyValueDAO # noqa: PLC0415
|
||||
from superset.daos.key_value import KeyValueDAO
|
||||
|
||||
try:
|
||||
KeyValueDAO.delete_expired_entries(RESOURCE)
|
||||
@@ -112,7 +112,7 @@ class SupersetMetastoreCache(BaseCache):
|
||||
|
||||
def get(self, key: str) -> Any:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.daos.key_value import KeyValueDAO # noqa: PLC0415
|
||||
from superset.daos.key_value import KeyValueDAO
|
||||
|
||||
return KeyValueDAO.get_value(RESOURCE, self.get_key(key), self.codec)
|
||||
|
||||
@@ -125,6 +125,6 @@ class SupersetMetastoreCache(BaseCache):
|
||||
@transaction()
|
||||
def delete(self, key: str) -> Any:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.daos.key_value import KeyValueDAO # noqa: PLC0415
|
||||
from superset.daos.key_value import KeyValueDAO
|
||||
|
||||
return KeyValueDAO.delete_entry(RESOURCE, self.get_key(key))
|
||||
|
||||
@@ -53,7 +53,7 @@ class SSHManager:
|
||||
ssh_tunnel: "SSHTunnel",
|
||||
sqlalchemy_database_uri: str,
|
||||
) -> sshtunnel.SSHTunnelForwarder:
|
||||
from superset.utils.ssh_tunnel import get_default_port # noqa: PLC0415
|
||||
from superset.utils.ssh_tunnel import get_default_port
|
||||
|
||||
url = make_url_safe(sqlalchemy_database_uri)
|
||||
backend = url.get_backend_name()
|
||||
|
||||
@@ -276,9 +276,7 @@ def get_extensions() -> dict[str, LoadedExtension]:
|
||||
|
||||
# Load extensions from discovery path (.supx files)
|
||||
if extensions_path := current_app.config.get("EXTENSIONS_PATH"):
|
||||
from superset.extensions.discovery import ( # noqa: PLC0415
|
||||
discover_and_load_extensions,
|
||||
)
|
||||
from superset.extensions.discovery import discover_and_load_extensions
|
||||
|
||||
for extension in discover_and_load_extensions(extensions_path):
|
||||
extension_id = extension.manifest.id
|
||||
|
||||
@@ -285,7 +285,7 @@ class ExtraCache:
|
||||
"""
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.views.utils import get_form_data # noqa: PLC0415
|
||||
from superset.views.utils import get_form_data
|
||||
|
||||
if has_request_context() and request.args.get(param):
|
||||
return request.args.get(param, default)
|
||||
@@ -407,7 +407,7 @@ class ExtraCache:
|
||||
:return: returns a list of filters
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.views.utils import get_form_data # noqa: PLC0415
|
||||
from superset.views.utils import get_form_data
|
||||
|
||||
form_data, _ = get_form_data()
|
||||
convert_legacy_filters_into_adhoc(form_data)
|
||||
@@ -512,7 +512,7 @@ class ExtraCache:
|
||||
:return: The corresponding time filter.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.views.utils import get_form_data # noqa: PLC0415
|
||||
from superset.views.utils import get_form_data
|
||||
|
||||
form_data, _ = get_form_data()
|
||||
convert_legacy_filters_into_adhoc(form_data)
|
||||
@@ -948,7 +948,7 @@ class PrestoTemplateProcessor(JinjaTemplateProcessor):
|
||||
"""
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.db_engine_specs.presto import PrestoEngineSpec # noqa: PLC0415
|
||||
from superset.db_engine_specs.presto import PrestoEngineSpec
|
||||
|
||||
table_name, schema = self._schema_table(table_name, self._schema)
|
||||
return cast(PrestoEngineSpec, self._database.db_engine_spec).latest_partition(
|
||||
@@ -959,7 +959,7 @@ class PrestoTemplateProcessor(JinjaTemplateProcessor):
|
||||
table_name, schema = self._schema_table(table_name, self._schema)
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.db_engine_specs.presto import PrestoEngineSpec # noqa: PLC0415
|
||||
from superset.db_engine_specs.presto import PrestoEngineSpec
|
||||
|
||||
return cast(
|
||||
PrestoEngineSpec, self._database.db_engine_spec
|
||||
@@ -1052,7 +1052,7 @@ def dataset_macro(
|
||||
the underlying dataset.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.daos.dataset import DatasetDAO # noqa: PLC0415
|
||||
from superset.daos.dataset import DatasetDAO
|
||||
|
||||
dataset = DatasetDAO.find_by_id(dataset_id)
|
||||
if not dataset:
|
||||
@@ -1081,8 +1081,8 @@ def get_dataset_id_from_context(metric_key: str) -> int:
|
||||
:returns: the dataset ID.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.daos.chart import ChartDAO # noqa: PLC0415
|
||||
from superset.views.utils import loads_request_json # noqa: PLC0415
|
||||
from superset.daos.chart import ChartDAO
|
||||
from superset.views.utils import loads_request_json
|
||||
|
||||
form_data: dict[str, Any] = {}
|
||||
exc_message = _(
|
||||
@@ -1134,7 +1134,7 @@ def metric_macro(
|
||||
:returns: the macro SQL syntax.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.daos.dataset import DatasetDAO # noqa: PLC0415
|
||||
from superset.daos.dataset import DatasetDAO
|
||||
|
||||
if not dataset_id:
|
||||
dataset_id = get_dataset_id_from_context(metric_key)
|
||||
|
||||
@@ -260,6 +260,140 @@ def merge_extra_form_data_filters_into_query(
|
||||
merge_form_data_filters_into_query(query, extra_query_form_data)
|
||||
|
||||
|
||||
def _deck_gl_spatial_cols(spatial: dict[str, Any] | None) -> list[str]:
|
||||
"""Return the column names referenced by a single Deck.gl spatial control."""
|
||||
if not isinstance(spatial, dict):
|
||||
return []
|
||||
spatial_type = spatial.get("type")
|
||||
if spatial_type == "latlong":
|
||||
return [c for c in [spatial.get("lonCol"), spatial.get("latCol")] if c]
|
||||
if spatial_type == "delimited":
|
||||
return [c for c in [spatial.get("lonlatCol")] if c]
|
||||
if spatial_type == "geohash":
|
||||
return [c for c in [spatial.get("geohashCol")] if c]
|
||||
return []
|
||||
|
||||
|
||||
def _deck_gl_tooltip_cols(tooltip_contents: list[Any] | None) -> list[str]:
|
||||
"""Return column names from Deck.gl tooltip_contents config."""
|
||||
cols: list[str] = []
|
||||
for item in tooltip_contents or []:
|
||||
if isinstance(item, str):
|
||||
cols.append(item)
|
||||
elif isinstance(item, dict) and item.get("item_type") == "column":
|
||||
col = item.get("column_name")
|
||||
if isinstance(col, str) and col:
|
||||
cols.append(col)
|
||||
return cols
|
||||
|
||||
|
||||
def _is_metric_ref(value: Any) -> bool:
|
||||
"""Return True if value is a metric reference (dict or non-numeric string).
|
||||
|
||||
Deck.gl size/metric fields hold either a dict metric definition or a
|
||||
simple saved-metric string key (e.g. "count"). Scalar numeric strings
|
||||
like "100" are fixed display settings and must not be treated as metrics.
|
||||
"""
|
||||
if isinstance(value, dict):
|
||||
return True
|
||||
if isinstance(value, str) and value:
|
||||
try:
|
||||
float(value)
|
||||
return False
|
||||
except ValueError:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _deck_gl_null_filters(form_data: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""Build IS NOT NULL simple filters for Deck.gl spatial and data columns.
|
||||
|
||||
Mirrors BaseDeckGLViz.add_null_filters() behavior: spatial control columns,
|
||||
line_column, and the geojson column are filtered for non-null values by
|
||||
default.
|
||||
"""
|
||||
seen: set[str] = set()
|
||||
result: list[dict[str, Any]] = []
|
||||
for key in ("spatial", "start_spatial", "end_spatial"):
|
||||
for col in _deck_gl_spatial_cols(form_data.get(key)):
|
||||
if col not in seen:
|
||||
seen.add(col)
|
||||
result.append({"col": col, "op": "IS NOT NULL", "val": ""})
|
||||
for field in ("line_column", "geojson"):
|
||||
data_col = form_data.get(field)
|
||||
if isinstance(data_col, str) and data_col and data_col not in seen:
|
||||
seen.add(data_col)
|
||||
result.append({"col": data_col, "op": "IS NOT NULL", "val": ""})
|
||||
return result
|
||||
|
||||
|
||||
def _resolve_deck_gl_metrics(
|
||||
form_data: dict[str, Any], viz_type: str = ""
|
||||
) -> list[Any]:
|
||||
"""Extract metrics for Deck.gl chart types.
|
||||
|
||||
deck_geojson.query_obj() forces metrics=[] regardless of form_data.
|
||||
For other types, size/metric values are included when they are metric
|
||||
references (dicts or non-numeric strings); numeric scalars like "100"
|
||||
are fixed display settings and are excluded.
|
||||
deck_scatter and deck_polygon can additionally store metric-backed
|
||||
values in point_radius_fixed (radius for scatter, elevation for polygon).
|
||||
"""
|
||||
if viz_type == "deck_geojson":
|
||||
return []
|
||||
metrics: list[Any] = []
|
||||
for field in ("size", "metric"):
|
||||
m = form_data.get(field)
|
||||
if _is_metric_ref(m):
|
||||
metrics.append(m)
|
||||
prf = form_data.get("point_radius_fixed")
|
||||
if isinstance(prf, dict) and prf.get("type") == "metric":
|
||||
value = prf.get("value")
|
||||
if value:
|
||||
metrics.append(value)
|
||||
elif _is_metric_ref(prf):
|
||||
# Legacy deck_scatter: point_radius_fixed can be a bare metric key string
|
||||
metrics.append(prf)
|
||||
return metrics
|
||||
|
||||
|
||||
def resolve_deck_gl_columns(form_data: dict[str, Any]) -> list[str]:
|
||||
"""Extract SQL column names for Deck.gl chart types from form_data.
|
||||
|
||||
Deck.gl charts use spatial controls (lat/lon pairs, geohash, etc.)
|
||||
rather than the standard metrics/groupby structure. This function
|
||||
maps those spatial control configs to the actual column names
|
||||
needed by the SQL query.
|
||||
"""
|
||||
seen: set[str] = set()
|
||||
columns: list[str] = []
|
||||
|
||||
def _add(col: str | None) -> None:
|
||||
if col and isinstance(col, str) and col not in seen:
|
||||
seen.add(col)
|
||||
columns.append(col)
|
||||
|
||||
# Most Deck.gl types use "spatial"; arc charts use start/end spatial
|
||||
for key in ("spatial", "start_spatial", "end_spatial"):
|
||||
for col in _deck_gl_spatial_cols(form_data.get(key)):
|
||||
_add(col)
|
||||
|
||||
# deck_path / deck_polygon use a line column; deck_geojson uses geojson
|
||||
for field in ("line_column", "geojson", "dimension"):
|
||||
_add(form_data.get(field))
|
||||
|
||||
for col in form_data.get("js_columns") or []:
|
||||
if isinstance(col, str):
|
||||
_add(col)
|
||||
|
||||
for col in _deck_gl_tooltip_cols(form_data.get("tooltip_contents")):
|
||||
_add(col)
|
||||
|
||||
_add(form_data.get("cross_filter_column"))
|
||||
|
||||
return columns
|
||||
|
||||
|
||||
def resolve_metrics(form_data: dict[str, Any], viz_type: str) -> list[Any]:
|
||||
"""Extract metrics from form_data, handling chart-type-specific fields."""
|
||||
if viz_type == "bubble":
|
||||
@@ -423,6 +557,25 @@ def build_query_dicts_from_form_data(
|
||||
or (getattr(chart, "viz_type", "") if chart else "")
|
||||
or ""
|
||||
)
|
||||
|
||||
# Deck.gl charts use spatial column configs rather than the standard
|
||||
# metrics / groupby fields. Extract columns from the spatial controls.
|
||||
if viz_type.startswith("deck_"):
|
||||
deck_columns = resolve_deck_gl_columns(form_data)
|
||||
deck_metrics = _resolve_deck_gl_metrics(form_data, viz_type)
|
||||
qd = _build_single_query_dict(
|
||||
form_data,
|
||||
deck_columns,
|
||||
deck_metrics,
|
||||
row_limit=row_limit,
|
||||
order_desc=order_desc,
|
||||
)
|
||||
if form_data.get("filter_nulls", True):
|
||||
null_filters = _deck_gl_null_filters(form_data)
|
||||
if null_filters:
|
||||
qd["filters"] = [*(qd.get("filters") or []), *null_filters]
|
||||
return [qd]
|
||||
|
||||
is_timeseries = (
|
||||
viz_type.startswith("echarts_timeseries") or viz_type == "mixed_timeseries"
|
||||
)
|
||||
|
||||
@@ -340,31 +340,10 @@ async def get_chart_data( # noqa: C901
|
||||
# groupby-like fields (entity, series, columns):
|
||||
# world_map, treemap_v2, sunburst_v2, gauge_chart
|
||||
# Bubble charts use x/y/size as separate metric fields.
|
||||
# Deck.gl charts (deck_arc, deck_scatter, etc.) use spatial
|
||||
# column configs (lat/lon, geohash, etc.) instead.
|
||||
viz_type = chart.viz_type or ""
|
||||
|
||||
# Deck.gl chart types store spatial data (lat/lon)
|
||||
# rather than traditional metrics/groupby. They
|
||||
# require a saved query_context to retrieve data.
|
||||
# Match by prefix to cover all current and future
|
||||
# deck.gl viz types (deck_arc, deck_scatter, etc.).
|
||||
if viz_type.startswith("deck_"):
|
||||
await ctx.warning(
|
||||
"Chart %s is a deck.gl visualization (%s) with no "
|
||||
"saved query_context. Data retrieval requires "
|
||||
"re-saving the chart in Superset." % (chart.id, viz_type)
|
||||
)
|
||||
return ChartError(
|
||||
error=(
|
||||
f"Chart {chart.id} is a deck.gl visualization "
|
||||
f"(type: {viz_type}) with no saved query_context. "
|
||||
f"Deck.gl charts use spatial data (lat/lon) that "
|
||||
f"cannot be reconstructed from form_data alone. "
|
||||
f"Please open this chart in Superset and re-save "
|
||||
f"it to generate a query_context."
|
||||
),
|
||||
error_type="MissingQueryContext",
|
||||
)
|
||||
|
||||
fallback_queries = build_query_dicts_from_form_data(
|
||||
form_data,
|
||||
chart.datasource_id,
|
||||
|
||||
@@ -1317,7 +1317,7 @@ class Database(CoreDatabase, AuditMixinNullable, ImportExportMixin): # pylint:
|
||||
:param options: QueryOptions with execution settings
|
||||
:returns: QueryResult with status, data, and metadata
|
||||
"""
|
||||
from superset.sql.execution import SQLExecutor # noqa: PLC0415
|
||||
from superset.sql.execution import SQLExecutor
|
||||
|
||||
return SQLExecutor(self).execute(sql, options)
|
||||
|
||||
@@ -1333,7 +1333,7 @@ class Database(CoreDatabase, AuditMixinNullable, ImportExportMixin): # pylint:
|
||||
:param options: QueryOptions with execution settings
|
||||
:returns: AsyncQueryHandle for tracking the query
|
||||
"""
|
||||
from superset.sql.execution import SQLExecutor # noqa: PLC0415
|
||||
from superset.sql.execution import SQLExecutor
|
||||
|
||||
return SQLExecutor(self).execute_async(sql, options)
|
||||
|
||||
|
||||
@@ -1375,9 +1375,7 @@ class ExploreMixin: # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
# Import here to avoid circular dependency
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.common.utils.query_cache_manager import ( # noqa: PLC0415
|
||||
QueryCacheManager,
|
||||
)
|
||||
from superset.common.utils.query_cache_manager import QueryCacheManager
|
||||
|
||||
# ensure query_object is immutable
|
||||
query_object_clone = copy.copy(query_object)
|
||||
@@ -2515,9 +2513,7 @@ class ExploreMixin: # pylint: disable=too-many-public-methods
|
||||
:return: Dict with validation result and any errors
|
||||
"""
|
||||
|
||||
from superset.sql_validators.base import ( # noqa: PLC0415
|
||||
SQLValidationAnnotation,
|
||||
)
|
||||
from superset.sql_validators.base import SQLValidationAnnotation
|
||||
|
||||
try:
|
||||
# Process template
|
||||
|
||||
@@ -342,9 +342,7 @@ class Slice( # pylint: disable=too-many-public-methods
|
||||
def get_query_context_factory(self) -> QueryContextFactory:
|
||||
if self.query_context_factory is None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.common.query_context_factory import ( # noqa: PLC0415
|
||||
QueryContextFactory,
|
||||
)
|
||||
from superset.common.query_context_factory import QueryContextFactory
|
||||
|
||||
self.query_context_factory = QueryContextFactory()
|
||||
return self.query_context_factory
|
||||
@@ -365,7 +363,7 @@ def id_or_uuid_filter(id_or_uuid: str | int) -> BinaryExpression:
|
||||
|
||||
def set_related_perm(_mapper: Mapper, _connection: Connection, target: Slice) -> None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.daos.datasource import DatasourceDAO # noqa: PLC0415
|
||||
from superset.daos.datasource import DatasourceDAO
|
||||
|
||||
src_class = DatasourceDAO.sources[target.datasource_type]
|
||||
if id_ := target.datasource_id:
|
||||
|
||||
@@ -238,7 +238,7 @@ class Query(
|
||||
|
||||
@property
|
||||
def columns(self) -> list["TableColumn"]:
|
||||
from superset.connectors.sqla.models import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import ( # pylint: disable=import-outside-toplevel
|
||||
TableColumn,
|
||||
)
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ def _log_audit_event(action: str, payload: dict[str, Any]) -> None:
|
||||
configured implementation (DBEventLogger, S3EventLogger, etc.)
|
||||
receives these security audit events.
|
||||
"""
|
||||
from superset.extensions import ( # noqa: PLC0415
|
||||
from superset.extensions import (
|
||||
event_logger, # pylint: disable=import-outside-toplevel
|
||||
)
|
||||
|
||||
@@ -654,7 +654,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
|
||||
def request_loader(self, request: Request) -> Optional[User]:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.extensions import feature_flag_manager # noqa: PLC0415
|
||||
from superset.extensions import feature_flag_manager
|
||||
|
||||
if feature_flag_manager.is_feature_enabled("EMBEDDED_SUPERSET"):
|
||||
return self.get_guest_user_from_request(request)
|
||||
@@ -799,7 +799,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
:param datasource: The datasource
|
||||
:returns: Whether the user can access the datasource's schema
|
||||
"""
|
||||
from superset.connectors.sqla.models import BaseDatasource # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import BaseDatasource
|
||||
|
||||
# Admin/superuser override
|
||||
if self.can_access_all_datasources():
|
||||
@@ -847,7 +847,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
Return True if an embedded user or DASHBOARD_RBAC user can drill a dataset.
|
||||
"""
|
||||
from superset import is_feature_enabled # noqa: PLC0415
|
||||
from superset import is_feature_enabled
|
||||
|
||||
if (
|
||||
(
|
||||
@@ -909,7 +909,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
:returns: Whether the user has drill access.
|
||||
"""
|
||||
|
||||
from superset.models.slice import Slice # noqa: PLC0415
|
||||
from superset.models.slice import Slice
|
||||
|
||||
# Drill to Detail: no slice/chart context, dataset must belong to the dashboard
|
||||
if (
|
||||
@@ -1097,7 +1097,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
user_datasources = set()
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
|
||||
user_datasources.update(
|
||||
self.session.query(SqlaTable)
|
||||
@@ -1208,7 +1208,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
|
||||
default_catalog = database.get_default_catalog()
|
||||
catalog = catalog or default_catalog
|
||||
@@ -1274,7 +1274,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
:returns: The set of accessible database catalogs
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
|
||||
if hierarchical and self.can_access_database(database):
|
||||
return catalogs
|
||||
@@ -1339,7 +1339,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
:returns: The list of accessible SQL tables w/ schema
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
|
||||
if self.can_access_database(database):
|
||||
return datasource_names
|
||||
@@ -1428,8 +1428,8 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: PLC0415
|
||||
from superset.models import core as models # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.models import core as models
|
||||
|
||||
logger.info("Fetching a set of all perms to lookup which ones are missing")
|
||||
all_pvs = {
|
||||
@@ -1461,7 +1461,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
existing_pvs: set[tuple[str, str]],
|
||||
) -> None:
|
||||
"""Backfill perm columns and create missing PVMs for semantic models."""
|
||||
from superset.semantic_layers.models import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.semantic_layers.models import ( # pylint: disable=import-outside-toplevel
|
||||
SemanticLayer,
|
||||
SemanticView,
|
||||
)
|
||||
@@ -1959,10 +1959,10 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
:param target: The database object
|
||||
:return: A list of changed view menus (permission resource names)
|
||||
""" # noqa: E501
|
||||
from superset.connectors.sqla.models import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import ( # pylint: disable=import-outside-toplevel
|
||||
SqlaTable,
|
||||
)
|
||||
from superset.models.slice import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.models.slice import ( # pylint: disable=import-outside-toplevel
|
||||
Slice,
|
||||
)
|
||||
|
||||
@@ -2039,7 +2039,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
:param target: The changed dataset object
|
||||
:return:
|
||||
"""
|
||||
from superset.models.core import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.models.core import ( # pylint: disable=import-outside-toplevel
|
||||
Database,
|
||||
)
|
||||
|
||||
@@ -2152,7 +2152,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
:return:
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
|
||||
# Check if watched fields have changed
|
||||
table = SqlaTable.__table__ # pylint: disable=no-member
|
||||
@@ -2245,10 +2245,10 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
:param target: Dataset that was updated
|
||||
:return:
|
||||
"""
|
||||
from superset.connectors.sqla.models import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import ( # pylint: disable=import-outside-toplevel
|
||||
SqlaTable,
|
||||
)
|
||||
from superset.models.slice import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.models.slice import ( # pylint: disable=import-outside-toplevel
|
||||
Slice,
|
||||
)
|
||||
|
||||
@@ -2318,10 +2318,10 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
old_permission_name,
|
||||
new_permission_name,
|
||||
)
|
||||
from superset.connectors.sqla.models import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import ( # pylint: disable=import-outside-toplevel
|
||||
SqlaTable,
|
||||
)
|
||||
from superset.models.slice import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.models.slice import ( # pylint: disable=import-outside-toplevel
|
||||
Slice,
|
||||
)
|
||||
|
||||
@@ -2379,7 +2379,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
|
||||
Creates the datasource_access PVM and stores the perm string on the row.
|
||||
"""
|
||||
from superset.semantic_layers.models import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.semantic_layers.models import ( # pylint: disable=import-outside-toplevel
|
||||
SemanticLayer,
|
||||
)
|
||||
|
||||
@@ -2406,7 +2406,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
Renames the FAB ViewMenu so the PVM stays in sync with the layer name.
|
||||
Also cascades the rename to all semantic view perms under this layer.
|
||||
"""
|
||||
from superset.semantic_layers.models import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.semantic_layers.models import ( # pylint: disable=import-outside-toplevel
|
||||
SemanticLayer,
|
||||
SemanticView,
|
||||
)
|
||||
@@ -2494,7 +2494,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
Looks up the layer name via connection since the ORM relationship may
|
||||
not be loaded during event handling.
|
||||
"""
|
||||
from superset.semantic_layers.models import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.semantic_layers.models import ( # pylint: disable=import-outside-toplevel
|
||||
SemanticLayer,
|
||||
SemanticView,
|
||||
)
|
||||
@@ -2526,7 +2526,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
Looks up the layer name via connection since the ORM relationship may
|
||||
not be loaded during event handling.
|
||||
"""
|
||||
from superset.semantic_layers.models import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.semantic_layers.models import ( # pylint: disable=import-outside-toplevel
|
||||
SemanticLayer,
|
||||
SemanticView,
|
||||
)
|
||||
@@ -2883,12 +2883,12 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
:raises SupersetSecurityException: If the user cannot access the resource
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset import is_feature_enabled # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: PLC0415
|
||||
from superset.models.dashboard import Dashboard # noqa: PLC0415
|
||||
from superset.models.slice import Slice # noqa: PLC0415
|
||||
from superset.models.sql_lab import Query # noqa: PLC0415
|
||||
from superset.utils.core import shortid # noqa: PLC0415
|
||||
from superset import is_feature_enabled
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.models.sql_lab import Query
|
||||
from superset.utils.core import shortid
|
||||
|
||||
if sql and database:
|
||||
query = Query(
|
||||
@@ -2922,7 +2922,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
# If the DB engine spec doesn't implement the logic the schema is read
|
||||
# from the SQLAlchemy URI if possible; if not, we use the SQLAlchemy
|
||||
# inspector to read it.
|
||||
from superset.models.sql_lab import Query # noqa: PLC0415
|
||||
from superset.models.sql_lab import Query
|
||||
|
||||
default_schema = database.get_default_schema_for_query(
|
||||
cast(Query, query),
|
||||
@@ -3215,7 +3215,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
return cache[cache_key]
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.connectors.sqla.models import ( # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import (
|
||||
RLSFilterRoles,
|
||||
RLSFilterTables,
|
||||
RowLevelSecurityFilter,
|
||||
@@ -3299,7 +3299,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
return
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.connectors.sqla.models import ( # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import (
|
||||
RLSFilterRoles,
|
||||
RLSFilterTables,
|
||||
RowLevelSecurityFilter,
|
||||
@@ -3410,11 +3410,11 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
@staticmethod
|
||||
def validate_guest_token_resources(resources: GuestTokenResources) -> None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.commands.dashboard.embedded.exceptions import ( # noqa: PLC0415
|
||||
from superset.commands.dashboard.embedded.exceptions import (
|
||||
EmbeddedDashboardNotFoundError,
|
||||
)
|
||||
from superset.daos.dashboard import EmbeddedDashboardDAO # noqa: PLC0415
|
||||
from superset.models.dashboard import Dashboard # noqa: PLC0415
|
||||
from superset.daos.dashboard import EmbeddedDashboardDAO
|
||||
from superset.models.dashboard import Dashboard
|
||||
|
||||
for resource in resources:
|
||||
if resource["type"] == GuestTokenResourceType.DASHBOARD.value:
|
||||
@@ -3505,7 +3505,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
@staticmethod
|
||||
def is_guest_user(user: Optional[Any] = None) -> bool:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset import is_feature_enabled # noqa: PLC0415
|
||||
from superset import is_feature_enabled
|
||||
|
||||
if not is_feature_enabled("EMBEDDED_SUPERSET"):
|
||||
return False
|
||||
@@ -3598,10 +3598,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
||||
# temporal change to remove the roles view from the security menu,
|
||||
# after migrating all views to frontend, we will set FAB_ADD_SECURITY_VIEWS = False
|
||||
def register_views(self) -> None:
|
||||
from superset.views.auth import ( # noqa: PLC0415
|
||||
SupersetAuthView,
|
||||
SupersetRegisterUserView,
|
||||
)
|
||||
from superset.views.auth import SupersetAuthView, SupersetRegisterUserView
|
||||
|
||||
if self.register_superset_auth_view:
|
||||
self.auth_view = self.appbuilder.add_view_no_menu(SupersetAuthView)
|
||||
|
||||
@@ -36,9 +36,7 @@ def _sl(legacy: str, semantic: str) -> str:
|
||||
# Imported lazily to avoid a circular import at module load time
|
||||
# (superset.semantic_layers.labels is imported by superset.initialization,
|
||||
# which is itself imported during superset package initialization).
|
||||
from superset import ( # noqa: PLC0415
|
||||
feature_flag_manager, # pylint: disable=import-outside-toplevel
|
||||
)
|
||||
from superset import feature_flag_manager # pylint: disable=import-outside-toplevel
|
||||
|
||||
return (
|
||||
semantic
|
||||
|
||||
@@ -150,7 +150,7 @@ class SemanticLayer(AuditMixinNullable, Model):
|
||||
connection: Connection,
|
||||
target: "SemanticLayer",
|
||||
) -> None:
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
security_manager.semantic_layer_after_insert(mapper, connection, target)
|
||||
|
||||
@@ -160,7 +160,7 @@ class SemanticLayer(AuditMixinNullable, Model):
|
||||
connection: Connection,
|
||||
target: "SemanticLayer",
|
||||
) -> None:
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
security_manager.semantic_layer_before_update(mapper, connection, target)
|
||||
|
||||
@@ -170,7 +170,7 @@ class SemanticLayer(AuditMixinNullable, Model):
|
||||
connection: Connection,
|
||||
target: "SemanticLayer",
|
||||
) -> None:
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
security_manager.semantic_layer_after_delete(mapper, connection, target)
|
||||
|
||||
@@ -242,7 +242,7 @@ class SemanticView(AuditMixinNullable, Model):
|
||||
connection: Connection,
|
||||
target: "SemanticView",
|
||||
) -> None:
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
security_manager.semantic_view_after_insert(mapper, connection, target)
|
||||
|
||||
@@ -252,7 +252,7 @@ class SemanticView(AuditMixinNullable, Model):
|
||||
connection: Connection,
|
||||
target: "SemanticView",
|
||||
) -> None:
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
security_manager.semantic_view_before_update(mapper, connection, target)
|
||||
|
||||
@@ -262,7 +262,7 @@ class SemanticView(AuditMixinNullable, Model):
|
||||
connection: Connection,
|
||||
target: "SemanticView",
|
||||
) -> None:
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
security_manager.semantic_view_after_delete(mapper, connection, target)
|
||||
|
||||
@@ -482,13 +482,9 @@ class SemanticView(AuditMixinNullable, Model):
|
||||
|
||||
def raise_for_access(self) -> None:
|
||||
"""Check that the user has access to this semantic view."""
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset.errors import ( # noqa: PLC0415
|
||||
ErrorLevel,
|
||||
SupersetError,
|
||||
SupersetErrorType,
|
||||
)
|
||||
from superset.exceptions import SupersetSecurityException # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
|
||||
from superset.exceptions import SupersetSecurityException
|
||||
|
||||
if security_manager.can_access_all_datasources():
|
||||
return
|
||||
|
||||
@@ -109,7 +109,7 @@ def _handle_query_error(
|
||||
|
||||
def _serialize_payload(payload: dict[Any, Any]) -> bytes:
|
||||
"""Serialize payload for storage based on RESULTS_BACKEND_USE_MSGPACK config."""
|
||||
from superset import results_backend_use_msgpack # noqa: PLC0415
|
||||
from superset import results_backend_use_msgpack
|
||||
|
||||
if results_backend_use_msgpack:
|
||||
return msgpack.dumps(payload, default=json.json_iso_dttm_ser, use_bin_type=True)
|
||||
@@ -298,8 +298,8 @@ def _serialize_result_set(
|
||||
:param result_set: Query result set to serialize
|
||||
:returns: Tuple of (serialized_data, columns)
|
||||
"""
|
||||
from superset import results_backend_use_msgpack # noqa: PLC0415
|
||||
from superset.dataframe import df_to_records # noqa: PLC0415
|
||||
from superset import results_backend_use_msgpack
|
||||
from superset.dataframe import df_to_records
|
||||
|
||||
if results_backend_use_msgpack:
|
||||
if has_app_context():
|
||||
|
||||
@@ -122,7 +122,7 @@ def execute_sql_with_cursor(
|
||||
:returns: List of (statement_sql, result_set, execution_time_ms, rowcount) tuples
|
||||
Returns empty list if stopped. Raises exception on error (fail-fast).
|
||||
"""
|
||||
from superset.result_set import SupersetResultSet # noqa: PLC0415
|
||||
from superset.result_set import SupersetResultSet
|
||||
|
||||
total = len(statements)
|
||||
if total == 0:
|
||||
@@ -214,7 +214,7 @@ class SQLExecutor:
|
||||
|
||||
See superset_core.api.models.Database.execute() for full documentation.
|
||||
"""
|
||||
from superset_core.queries.types import ( # noqa: PLC0415
|
||||
from superset_core.queries.types import (
|
||||
QueryOptions as QueryOptionsType,
|
||||
QueryResult as QueryResultType,
|
||||
QueryStatus,
|
||||
@@ -341,7 +341,7 @@ class SQLExecutor:
|
||||
|
||||
See superset_core.api.models.Database.execute_async() for full documentation.
|
||||
"""
|
||||
from superset_core.queries.types import ( # noqa: PLC0415
|
||||
from superset_core.queries.types import (
|
||||
QueryOptions as QueryOptionsType,
|
||||
QueryResult as QueryResultType,
|
||||
QueryStatus,
|
||||
@@ -363,7 +363,7 @@ class SQLExecutor:
|
||||
|
||||
# DRY RUN: Return transformed SQL as completed async handle
|
||||
if opts.dry_run:
|
||||
from superset_core.queries.types import StatementResult # noqa: PLC0415
|
||||
from superset_core.queries.types import StatementResult
|
||||
|
||||
original_sqls = [stmt.format() for stmt in original_script.statements]
|
||||
transformed_sqls = [stmt.format() for stmt in transformed_script.statements]
|
||||
@@ -510,7 +510,7 @@ class SQLExecutor:
|
||||
:param query: Query model for progress tracking
|
||||
:returns: List of StatementResult objects
|
||||
"""
|
||||
from superset_core.queries.types import StatementResult # noqa: PLC0415
|
||||
from superset_core.queries.types import StatementResult
|
||||
|
||||
# Get original statement strings
|
||||
original_sqls = [stmt.format() for stmt in original_script.statements]
|
||||
@@ -578,7 +578,7 @@ class SQLExecutor:
|
||||
:param sql: SQL to log
|
||||
:param schema: Schema name
|
||||
"""
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
if log_query := app.config.get("QUERY_LOGGER"):
|
||||
log_query(
|
||||
@@ -607,9 +607,7 @@ class SQLExecutor:
|
||||
statements before the failure
|
||||
:returns: QueryResult with error status
|
||||
"""
|
||||
from superset_core.queries.types import ( # noqa: PLC0415
|
||||
QueryResult as QueryResultType,
|
||||
)
|
||||
from superset_core.queries.types import QueryResult as QueryResultType
|
||||
|
||||
return QueryResultType(
|
||||
status=status,
|
||||
@@ -631,7 +629,7 @@ class SQLExecutor:
|
||||
if template_params is None:
|
||||
return sql
|
||||
|
||||
from superset.jinja_context import get_template_processor # noqa: PLC0415
|
||||
from superset.jinja_context import get_template_processor
|
||||
|
||||
tp = get_template_processor(database=self.database)
|
||||
return tp.process_template(sql, **template_params)
|
||||
@@ -739,7 +737,7 @@ class SQLExecutor:
|
||||
:param catalog: Catalog name
|
||||
:param schema: Schema name
|
||||
"""
|
||||
from superset.utils.rls import apply_rls # noqa: PLC0415
|
||||
from superset.utils.rls import apply_rls
|
||||
|
||||
# Apply RLS to each statement in the script
|
||||
for statement in script.statements:
|
||||
@@ -763,7 +761,7 @@ class SQLExecutor:
|
||||
:param status: Initial QueryStatus (RUNNING for sync, PENDING for async)
|
||||
:returns: Query model instance
|
||||
"""
|
||||
from superset.models.sql_lab import Query as QueryModel # noqa: PLC0415
|
||||
from superset.models.sql_lab import Query as QueryModel
|
||||
|
||||
user_id = None
|
||||
if has_app_context() and hasattr(g, "user") and g.user:
|
||||
@@ -795,7 +793,7 @@ class SQLExecutor:
|
||||
:param opts: Query options
|
||||
:returns: Cached QueryResult if found, None otherwise
|
||||
"""
|
||||
from superset_core.queries.types import ( # noqa: PLC0415
|
||||
from superset_core.queries.types import (
|
||||
QueryResult as QueryResultType,
|
||||
QueryStatus,
|
||||
StatementResult,
|
||||
@@ -835,7 +833,7 @@ class SQLExecutor:
|
||||
:param sql: SQL query (for cache key)
|
||||
:param opts: Query options
|
||||
"""
|
||||
from superset_core.queries.types import QueryStatus # noqa: PLC0415
|
||||
from superset_core.queries.types import QueryStatus
|
||||
|
||||
if result.status != QueryStatus.SUCCESS:
|
||||
return
|
||||
@@ -851,7 +849,7 @@ class SQLExecutor:
|
||||
# Convert DataFrames to list-of-dicts so the cache backend
|
||||
# does not need to pickle pandas objects (which can fail to
|
||||
# deserialize correctly with some backends or pandas versions).
|
||||
import pandas as pd # noqa: PLC0415
|
||||
import pandas as pd
|
||||
|
||||
cached_data = {
|
||||
"statements": [
|
||||
@@ -908,9 +906,9 @@ class SQLExecutor:
|
||||
:param rendered_sql: Rendered SQL to execute
|
||||
:raises: Re-raises any exception after marking query as failed
|
||||
"""
|
||||
from superset.sql.execution.celery_task import execute_sql_task # noqa: PLC0415
|
||||
from superset.utils.core import get_username # noqa: PLC0415
|
||||
from superset.utils.dates import now_as_float # noqa: PLC0415
|
||||
from superset.sql.execution.celery_task import execute_sql_task
|
||||
from superset.utils.core import get_username
|
||||
from superset.utils.dates import now_as_float
|
||||
|
||||
try:
|
||||
task = execute_sql_task.delay(
|
||||
@@ -933,7 +931,7 @@ class SQLExecutor:
|
||||
:param query_id: ID of the Query model
|
||||
:returns: AsyncQueryHandle with configured methods
|
||||
"""
|
||||
from superset_core.queries.types import ( # noqa: PLC0415
|
||||
from superset_core.queries.types import (
|
||||
AsyncQueryHandle as AsyncQueryHandleType,
|
||||
QueryResult as QueryResultType,
|
||||
QueryStatus,
|
||||
@@ -972,7 +970,7 @@ class SQLExecutor:
|
||||
:param cached_result: The cached QueryResult
|
||||
:returns: AsyncQueryHandle that returns the cached data
|
||||
"""
|
||||
from superset_core.queries.types import ( # noqa: PLC0415
|
||||
from superset_core.queries.types import (
|
||||
AsyncQueryHandle as AsyncQueryHandleType,
|
||||
QueryResult as QueryResultType,
|
||||
QueryStatus,
|
||||
@@ -1003,11 +1001,9 @@ class SQLExecutor:
|
||||
@staticmethod
|
||||
def _get_async_query_status(query_id: int) -> Any:
|
||||
"""Get the current status of an async query."""
|
||||
from superset_core.queries.types import ( # noqa: PLC0415
|
||||
QueryStatus as QueryStatusType,
|
||||
)
|
||||
from superset_core.queries.types import QueryStatus as QueryStatusType
|
||||
|
||||
from superset.models.sql_lab import Query as QueryModel # noqa: PLC0415
|
||||
from superset.models.sql_lab import Query as QueryModel
|
||||
|
||||
query = db.session.query(QueryModel).filter_by(id=query_id).one_or_none()
|
||||
if not query:
|
||||
@@ -1026,14 +1022,14 @@ class SQLExecutor:
|
||||
@staticmethod
|
||||
def _get_async_query_result(query_id: int) -> Any:
|
||||
"""Get the result of an async query."""
|
||||
import pandas as pd # noqa: PLC0415
|
||||
from superset_core.queries.types import ( # noqa: PLC0415
|
||||
import pandas as pd
|
||||
from superset_core.queries.types import (
|
||||
QueryResult as QueryResultType,
|
||||
QueryStatus as QueryStatusType,
|
||||
StatementResult,
|
||||
)
|
||||
|
||||
from superset.models.sql_lab import Query as QueryModel # noqa: PLC0415
|
||||
from superset.models.sql_lab import Query as QueryModel
|
||||
|
||||
query = db.session.query(QueryModel).filter_by(id=query_id).one_or_none()
|
||||
if not query:
|
||||
@@ -1052,16 +1048,16 @@ class SQLExecutor:
|
||||
|
||||
# Fetch results from results backend
|
||||
if query.results_key:
|
||||
import msgpack # noqa: PLC0415
|
||||
import msgpack
|
||||
|
||||
from superset import results_backend_manager # noqa: PLC0415
|
||||
from superset import results_backend_manager
|
||||
|
||||
results_backend = results_backend_manager.results_backend
|
||||
if results_backend is not None:
|
||||
blob = results_backend.get(query.results_key)
|
||||
if blob:
|
||||
try:
|
||||
from superset.utils.core import zlib_decompress # noqa: PLC0415
|
||||
from superset.utils.core import zlib_decompress
|
||||
|
||||
payload = msgpack.loads(zlib_decompress(blob))
|
||||
|
||||
@@ -1111,7 +1107,7 @@ class SQLExecutor:
|
||||
@staticmethod
|
||||
def _cancel_async_query(query_id: int, database: Database) -> bool:
|
||||
"""Cancel an async query."""
|
||||
from superset.models.sql_lab import Query as QueryModel # noqa: PLC0415
|
||||
from superset.models.sql_lab import Query as QueryModel
|
||||
|
||||
query = db.session.query(QueryModel).filter_by(id=query_id).one_or_none()
|
||||
if not query:
|
||||
@@ -1132,11 +1128,8 @@ class SQLExecutor:
|
||||
:param query: Query model instance to cancel
|
||||
:returns: True if cancelled successfully, False otherwise
|
||||
"""
|
||||
from superset.constants import ( # noqa: PLC0415
|
||||
QUERY_CANCEL_KEY,
|
||||
QUERY_EARLY_CANCEL_KEY,
|
||||
)
|
||||
from superset.utils.core import QuerySource # noqa: PLC0415
|
||||
from superset.constants import QUERY_CANCEL_KEY, QUERY_EARLY_CANCEL_KEY
|
||||
from superset.utils.core import QuerySource
|
||||
|
||||
# Some engines implicitly handle cancellation
|
||||
if database.db_engine_spec.has_implicit_cancel():
|
||||
|
||||
@@ -1550,7 +1550,7 @@ def process_jinja_sql(
|
||||
:raises jinja2.exceptions.TemplateError: If the Jinjafied SQL could not be rendered
|
||||
"""
|
||||
|
||||
from superset.jinja_context import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from superset.jinja_context import ( # pylint: disable=import-outside-toplevel
|
||||
get_template_processor,
|
||||
)
|
||||
|
||||
@@ -1605,7 +1605,7 @@ def sanitize_clause(clause: str, engine: str) -> str:
|
||||
try:
|
||||
statement = SQLStatement(clause, engine)
|
||||
dialect = SQLGLOT_DIALECTS.get(engine)
|
||||
from sqlglot.dialects.dialect import Dialect # noqa: PLC0415
|
||||
from sqlglot.dialects.dialect import Dialect
|
||||
|
||||
return Dialect.get_or_raise(dialect).generate(
|
||||
statement._parsed, # pylint: disable=protected-access
|
||||
|
||||
@@ -63,7 +63,7 @@ class PrestoDBSQLValidator(BaseSQLValidator):
|
||||
# these EXPLAIN queries done in validation as proper Query objects
|
||||
# in the superset ORM.
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from pyhive.exc import DatabaseError # noqa: PLC0415
|
||||
from pyhive.exc import DatabaseError
|
||||
|
||||
try:
|
||||
db_engine_spec.execute(cursor, sql, database)
|
||||
|
||||
@@ -67,9 +67,7 @@ class SqlQueryRenderImpl(SqlQueryRender):
|
||||
self._raise_template_exception(ex, execution_context)
|
||||
return "NOT_REACHABLE_CODE"
|
||||
except Exception as ex:
|
||||
from superset.jinja_context import ( # noqa: PLC0415
|
||||
UndefinedTemplateFunctionException,
|
||||
)
|
||||
from superset.jinja_context import UndefinedTemplateFunctionException
|
||||
|
||||
if isinstance(ex, UndefinedTemplateFunctionException):
|
||||
return query_model.sql.strip().strip(";")
|
||||
@@ -80,7 +78,7 @@ class SqlQueryRenderImpl(SqlQueryRender):
|
||||
execution_context: SqlJsonExecutionContext,
|
||||
sql: str,
|
||||
) -> str:
|
||||
from superset.sql.parse import SQLScript # noqa: PLC0415
|
||||
from superset.sql.parse import SQLScript
|
||||
|
||||
engine = execution_context.query.database.db_engine_spec.engine
|
||||
script = SQLScript(sql, engine)
|
||||
|
||||
+14
-14
@@ -18,14 +18,14 @@
|
||||
|
||||
|
||||
def register_sqla_event_listeners() -> None:
|
||||
import sqlalchemy as sqla # noqa: PLC0415
|
||||
import sqlalchemy as sqla
|
||||
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: PLC0415
|
||||
from superset.models.core import FavStar # noqa: PLC0415
|
||||
from superset.models.dashboard import Dashboard # noqa: PLC0415
|
||||
from superset.models.slice import Slice # noqa: PLC0415
|
||||
from superset.models.sql_lab import SavedQuery # noqa: PLC0415
|
||||
from superset.tags.models import ( # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.models.core import FavStar
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.models.sql_lab import SavedQuery
|
||||
from superset.tags.models import (
|
||||
ChartUpdater,
|
||||
DashboardUpdater,
|
||||
DatasetUpdater,
|
||||
@@ -54,14 +54,14 @@ def register_sqla_event_listeners() -> None:
|
||||
|
||||
|
||||
def clear_sqla_event_listeners() -> None:
|
||||
import sqlalchemy as sqla # noqa: PLC0415
|
||||
import sqlalchemy as sqla
|
||||
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: PLC0415
|
||||
from superset.models.core import FavStar # noqa: PLC0415
|
||||
from superset.models.dashboard import Dashboard # noqa: PLC0415
|
||||
from superset.models.slice import Slice # noqa: PLC0415
|
||||
from superset.models.sql_lab import SavedQuery # noqa: PLC0415
|
||||
from superset.tags.models import ( # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.models.core import FavStar
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.models.sql_lab import SavedQuery
|
||||
from superset.tags.models import (
|
||||
ChartUpdater,
|
||||
DashboardUpdater,
|
||||
DatasetUpdater,
|
||||
|
||||
@@ -421,7 +421,7 @@ class ThemeRestApi(BaseSupersetModelRestApi):
|
||||
is_system=False, # User-created themes are never system themes
|
||||
)
|
||||
|
||||
from superset.extensions import db # noqa: PLC0415
|
||||
from superset.extensions import db
|
||||
|
||||
db.session.add(new_theme)
|
||||
db.session.flush() # Flush to get the ID
|
||||
@@ -601,7 +601,7 @@ class ThemeRestApi(BaseSupersetModelRestApi):
|
||||
$ref: '#/components/responses/500'
|
||||
"""
|
||||
# Check if user is admin
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
if not security_manager.is_admin():
|
||||
return self.response(
|
||||
@@ -668,7 +668,7 @@ class ThemeRestApi(BaseSupersetModelRestApi):
|
||||
$ref: '#/components/responses/500'
|
||||
"""
|
||||
# Check if user is admin
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
if not security_manager.is_admin():
|
||||
return self.response(
|
||||
@@ -721,7 +721,7 @@ class ThemeRestApi(BaseSupersetModelRestApi):
|
||||
$ref: '#/components/responses/500'
|
||||
"""
|
||||
# Check if user is admin
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
if not security_manager.is_admin():
|
||||
return self.response(
|
||||
@@ -771,7 +771,7 @@ class ThemeRestApi(BaseSupersetModelRestApi):
|
||||
$ref: '#/components/responses/500'
|
||||
"""
|
||||
# Check if user is admin
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
|
||||
if not security_manager.is_admin():
|
||||
return self.response(
|
||||
|
||||
@@ -241,7 +241,7 @@ class CacheManager:
|
||||
|
||||
def _init_distributed_coordination(self, app: Flask) -> None:
|
||||
"""Initialize the distributed coordination backend (pub/sub, locks, streams)."""
|
||||
from superset.async_events.cache_backend import ( # noqa: PLC0415
|
||||
from superset.async_events.cache_backend import (
|
||||
RedisCacheBackend,
|
||||
RedisSentinelCacheBackend,
|
||||
)
|
||||
|
||||
@@ -595,7 +595,7 @@ def sanitize_url(url: str) -> str:
|
||||
return url
|
||||
|
||||
try:
|
||||
from urllib.parse import urlparse # noqa: PLC0415
|
||||
from urllib.parse import urlparse
|
||||
|
||||
parsed = urlparse(url)
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ def get_or_create_db(
|
||||
database_name: str, sqlalchemy_uri: str, always_create: bool | None = True
|
||||
) -> Database:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset import db # noqa: PLC0415
|
||||
from superset.models import core as models # noqa: PLC0415
|
||||
from superset import db
|
||||
from superset.models import core as models
|
||||
|
||||
database = (
|
||||
db.session.query(models.Database).filter_by(database_name=database_name).first()
|
||||
@@ -81,7 +81,7 @@ def get_main_database() -> Database:
|
||||
# with above function... think of how to refactor it
|
||||
def remove_database(database: Database) -> None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset import db # noqa: PLC0415
|
||||
from superset import db
|
||||
|
||||
db.session.delete(database)
|
||||
db.session.flush()
|
||||
|
||||
@@ -611,7 +611,7 @@ def get_since_until( # pylint: disable=too-many-arguments,too-many-locals,too-m
|
||||
# that is made available in some plugins behind the experimental
|
||||
# feature flag.
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset import feature_flag_manager # noqa: PLC0415
|
||||
from superset import feature_flag_manager
|
||||
|
||||
if feature_flag_manager.is_feature_enabled("CHART_PLUGINS_EXPERIMENTAL"):
|
||||
time_unit = ""
|
||||
|
||||
@@ -249,9 +249,7 @@ def transaction( # pylint: disable=redefined-outer-name
|
||||
def decorate(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||
@wraps(func)
|
||||
def wrapped(*args: Any, **kwargs: Any) -> Any:
|
||||
from superset import ( # noqa: PLC0415
|
||||
db, # pylint: disable=import-outside-toplevel
|
||||
)
|
||||
from superset import db # pylint: disable=import-outside-toplevel
|
||||
|
||||
if getattr(g, "in_transaction", False):
|
||||
# If already in a transaction, call the function directly
|
||||
|
||||
@@ -102,9 +102,7 @@ class EncryptedFieldFactory:
|
||||
|
||||
class SecretsMigrator:
|
||||
def __init__(self, previous_secret_key: str) -> None:
|
||||
from superset import ( # noqa: PLC0415
|
||||
db, # pylint: disable=import-outside-toplevel
|
||||
)
|
||||
from superset import db # pylint: disable=import-outside-toplevel
|
||||
|
||||
self._db = db
|
||||
self._previous_secret_key = previous_secret_key
|
||||
@@ -131,7 +129,7 @@ class SecretsMigrator:
|
||||
|
||||
:return: mapping of table name to (Table, {column name: EncryptedType})
|
||||
"""
|
||||
from flask_appbuilder import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415
|
||||
from flask_appbuilder import ( # pylint: disable=import-outside-toplevel
|
||||
Model as FABModel,
|
||||
)
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ def get_dataset_access_filters(
|
||||
*args: Any,
|
||||
) -> BooleanClauseList:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset import security_manager # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import Database # noqa: PLC0415
|
||||
from superset import security_manager
|
||||
from superset.connectors.sqla.models import Database
|
||||
|
||||
database_ids = security_manager.get_accessible_databases()
|
||||
perms = security_manager.user_view_menu_names("datasource_access")
|
||||
|
||||
@@ -178,8 +178,8 @@ class AbstractEventLogger(ABC):
|
||||
**payload_override: dict[str, Any] | None,
|
||||
) -> None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset import db # noqa: PLC0415
|
||||
from superset.views.core import get_form_data # noqa: PLC0415
|
||||
from superset import db
|
||||
from superset.views.core import get_form_data
|
||||
|
||||
referrer = request.referrer[:1000] if request and request.referrer else None
|
||||
|
||||
@@ -380,8 +380,8 @@ class DBEventLogger(AbstractEventLogger):
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset import db # noqa: PLC0415
|
||||
from superset.models.core import Log # noqa: PLC0415
|
||||
from superset import db
|
||||
from superset.models.core import Log
|
||||
|
||||
records = kwargs.get("records", [])
|
||||
curated_payload = kwargs.get("curated_payload")
|
||||
|
||||
@@ -180,7 +180,7 @@ def add_data(
|
||||
:param bool append: if the table already exists, append data or replace?
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.utils.database import get_example_database # noqa: PLC0415
|
||||
from superset.utils.database import get_example_database
|
||||
|
||||
database = get_example_database()
|
||||
table_exists = database.has_table(Table(table_name))
|
||||
|
||||
@@ -102,7 +102,7 @@ def get_oauth2_access_token(
|
||||
a valid token when they retry.
|
||||
""" # noqa: E501
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.models.core import DatabaseUserOAuth2Tokens # noqa: PLC0415
|
||||
from superset.models.core import DatabaseUserOAuth2Tokens
|
||||
|
||||
token = (
|
||||
db.session.query(DatabaseUserOAuth2Tokens)
|
||||
@@ -131,7 +131,7 @@ def refresh_oauth2_token(
|
||||
db_engine_spec: type[BaseEngineSpec],
|
||||
) -> str | None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.models.core import DatabaseUserOAuth2Tokens # noqa: PLC0415
|
||||
from superset.models.core import DatabaseUserOAuth2Tokens
|
||||
|
||||
# Use longer TTL for OAuth2 token refresh (may involve network calls)
|
||||
with DistributedLock(
|
||||
|
||||
@@ -56,7 +56,7 @@ def _prophet_fit_and_predict( # pylint: disable=too-many-arguments
|
||||
# `prophet` complains about `plotly` not being installed
|
||||
with suppress_logging("prophet.plot"):
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from prophet import Prophet # noqa: PLC0415
|
||||
from prophet import Prophet
|
||||
|
||||
prophet_logger = logging.getLogger("prophet.plot")
|
||||
prophet_logger.setLevel(logging.CRITICAL)
|
||||
|
||||
@@ -84,7 +84,7 @@ def get_predicates_for_table(
|
||||
table must be fully qualified, with catalog (null if the DB doesn't support) and
|
||||
schema.
|
||||
"""
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
|
||||
# if the dataset in the RLS has null catalog, match it when using the default
|
||||
# catalog
|
||||
@@ -158,7 +158,7 @@ def collect_rls_predicates_for_sql(
|
||||
(kept consistent with what's actually applied at query time).
|
||||
:return: List of RLS predicate strings that would be applied
|
||||
"""
|
||||
from superset.sql.parse import SQLScript # noqa: PLC0415
|
||||
from superset.sql.parse import SQLScript
|
||||
|
||||
try:
|
||||
parsed_script = SQLScript(sql, engine=database.db_engine_spec.engine)
|
||||
|
||||
@@ -125,7 +125,7 @@ def validate_webdriver_config() -> dict[str, Any]:
|
||||
Returns a dictionary with the status of available webdrivers
|
||||
and feature flags.
|
||||
"""
|
||||
from superset import feature_flag_manager # noqa: PLC0415
|
||||
from superset import feature_flag_manager
|
||||
|
||||
return {
|
||||
"selenium_available": True, # Always available as required dependency
|
||||
|
||||
@@ -129,9 +129,7 @@ class Api(BaseSupersetView):
|
||||
def get_query_context_factory(self) -> QueryContextFactory:
|
||||
if self.query_context_factory is None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from superset.common.query_context_factory import ( # noqa: PLC0415
|
||||
QueryContextFactory,
|
||||
)
|
||||
from superset.common.query_context_factory import QueryContextFactory
|
||||
|
||||
self.query_context_factory = QueryContextFactory()
|
||||
return self.query_context_factory
|
||||
|
||||
@@ -214,12 +214,9 @@ class Superset(BaseSupersetView):
|
||||
|
||||
@staticmethod
|
||||
def _generate_xlsx(viz_obj: BaseViz) -> FlaskResponse:
|
||||
import pandas as pd # noqa: PLC0415
|
||||
import pandas as pd
|
||||
|
||||
from superset.utils.excel import ( # noqa: PLC0415
|
||||
apply_column_types,
|
||||
df_to_excel,
|
||||
)
|
||||
from superset.utils.excel import apply_column_types, df_to_excel
|
||||
|
||||
payload = viz_obj.get_df_payload()
|
||||
df = payload.get("df")
|
||||
|
||||
@@ -117,7 +117,7 @@ class FilterRelatedTables(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
arg_name = "tables"
|
||||
|
||||
def apply(self, query: Query, value: Optional[Any]) -> Query:
|
||||
from superset.connectors.sqla.models import SqlaTable # noqa: PLC0415
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
|
||||
like_value = "%" + cast(str, value) + "%"
|
||||
return query.filter(SqlaTable.table_name.ilike(like_value))
|
||||
|
||||
@@ -40,6 +40,6 @@ def version() -> FlaskResponse:
|
||||
Return comprehensive version information including Git SHA
|
||||
and branch when available.
|
||||
"""
|
||||
from superset.utils.version import get_version_metadata # noqa: PLC0415
|
||||
from superset.utils.version import get_version_metadata
|
||||
|
||||
return jsonify(get_version_metadata())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user