Files
superset2/superset/commands/database/exceptions.py
Mike Bridge 04f8b700d7 feat(datasets): soft-delete and restore (#40130)
Co-authored-by: Mike Bridge <michael.bridge@ext.preset.io>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 08:57:08 -07:00

278 lines
8.2 KiB
Python

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from flask_babel import lazy_gettext as _
from marshmallow.validate import ValidationError
from superset.commands.exceptions import (
CommandException,
CommandInvalidError,
CreateFailedError,
DeleteFailedError,
ImportFailedError,
UpdateFailedError,
)
from superset.exceptions import SupersetErrorException, SupersetErrorsException
class DatabaseInvalidError(CommandInvalidError):
message = _("Database parameters are invalid.")
class DatabaseExistsValidationError(ValidationError):
"""
Marshmallow validation error for dataset already exists
"""
def __init__(self) -> None:
super().__init__(
_("A database with the same name already exists."),
field_name="database_name",
)
class DatabaseRequiredFieldValidationError(ValidationError):
def __init__(self, field_name: str) -> None:
super().__init__(
[_("Field is required")],
field_name=field_name,
)
class DatabaseExtraJSONValidationError(ValidationError):
"""
Marshmallow validation error for database encrypted extra must be a valid JSON
"""
def __init__(self, json_error: str = "") -> None:
super().__init__(
[
_(
"Field cannot be decoded by JSON. %(json_error)s",
json_error=json_error,
)
],
field_name="extra",
)
class DatabaseExtraValidationError(ValidationError):
"""
Marshmallow validation error for database encrypted extra must be a valid JSON
"""
def __init__(self, key: str = "") -> None:
super().__init__(
[
_(
"The metadata_params in Extra field "
"is not configured correctly. The key "
"%{key}s is invalid.",
key=key,
)
],
field_name="extra",
)
class DatabaseConnectionSyncPermissionsError(CommandException):
status = 500
message = _("Unable to sync permissions for this database connection.")
class DatabaseNotFoundError(CommandException):
status = 404
message = _("Database not found.")
class UserNotFoundInSessionError(CommandException):
status = 500
message = _("Could not validate the user in the current session.")
class DatabaseSchemaUploadNotAllowed(CommandException):
status = 403
message = _("Database schema is not allowed for csv uploads.")
class DatabaseUploadNotSupported(CommandException):
status = 422
message = _("Database type does not support file uploads.")
class DatabaseUploadFailed(CommandException):
status = 422
message = _("Database upload file failed")
class DatabaseUploadFileTooLarge(CommandException):
status = 413
message = _("Database upload file exceeds the maximum allowed size.")
class DatabaseUploadSoftDeletedDatasetExistsError(DatabaseUploadFailed):
"""The upload targets a table whose dataset sits soft-deleted in the trash.
Creating a new dataset over the same physical table would make an active
twin of the hidden row and permanently block its restore, so the upload
is refused before any file data is written. The message names only
executable recoveries (restore, or a different table name) — there is no
hard-delete/purge API surface until the purge work lands.
"""
def __init__(self, dataset_uuid: str) -> None:
super().__init__(
_(
"A soft-deleted dataset (uuid %(uuid)s) already references "
"this table. Restore it via POST "
"/api/v1/dataset/%(uuid)s/restore before uploading, or upload "
"to a different table name.",
uuid=dataset_uuid,
)
)
class DatabaseUploadSaveMetadataFailed(CommandException):
status = 500
message = _("Database upload file failed, while saving metadata")
class DatabaseCreateFailedError(CreateFailedError):
message = _("Database could not be created.")
class DatabaseUpdateFailedError(UpdateFailedError):
message = _("Database could not be updated.")
class DatabaseConnectionFailedError( # pylint: disable=too-many-ancestors
DatabaseCreateFailedError,
DatabaseUpdateFailedError,
):
message = _("Connection failed, please check your connection settings")
class MissingOAuth2TokenError(DatabaseUpdateFailedError):
"""
Exception for when the connection is missing an OAuth2 token
and it's not possible to initiate an OAuth2 dance.
"""
message = _("Missing OAuth2 token")
class DatabaseDeleteDatasetsExistFailedError(DeleteFailedError):
message = _("Cannot delete a database that has datasets attached")
class DatabaseDeleteSoftDeletedDatasetsExistFailedError(
DatabaseDeleteDatasetsExistFailedError
):
# Subclasses the live-datasets error so the existing API handler catches
# both; only the message differs, telling the operator that the blockers
# are hidden (soft-deleted) rows even though their dataset list looks empty.
message = _(
"Cannot delete a database whose only remaining datasets are "
"soft-deleted. Restore them (POST /api/v1/dataset/<uuid>/restore) "
"and delete them permanently once a purge capability ships, or "
"remove the underlying rows out-of-band, before deleting the "
"database."
)
class DatabaseDeleteFailedError(DeleteFailedError):
message = _("Database could not be deleted.")
class DatabaseDeleteFailedReportsExistError(DatabaseDeleteFailedError):
message = _("There are associated alerts or reports")
class DatabaseTestConnectionFailedError(SupersetErrorsException):
status = 422
message = _("Connection failed, please check your connection settings")
class DatabaseSecurityUnsafeError(CommandInvalidError):
message = _("Stopped an unsafe database connection")
class DatabaseTestConnectionDriverError(CommandInvalidError):
message = _("Could not load database driver")
class DatabaseTestConnectionUnexpectedError(SupersetErrorsException):
status = 422
message = _("Unexpected error occurred, please check your logs for details")
class DatabaseTablesUnexpectedError(CommandException):
status = 422
message = _("Unexpected error occurred, please check your logs for details")
class NoValidatorConfigFoundError(SupersetErrorException):
status = 422
message = _("no SQL validator is configured")
class NoValidatorFoundError(SupersetErrorException):
status = 422
message = _("No validator found (configured for the engine)")
class ValidatorSQLError(SupersetErrorException):
status = 422
message = _("Was unable to check your query")
class ValidatorSQLUnexpectedError(CommandException):
status = 422
message = _("An unexpected error occurred")
class ValidatorSQL400Error(SupersetErrorException):
status = 400
message = _("Was unable to check your query")
class DatabaseImportError(ImportFailedError):
message = _("Import database failed for an unknown reason")
class InvalidEngineError(SupersetErrorException):
status = 422
class DatabaseOfflineError(SupersetErrorException):
status = 422
class InvalidParametersError(SupersetErrorsException):
status = 422
class DatasetValidationError(CommandException):
status = 422
def __init__(self, err: Exception) -> None:
super().__init__(
_(
"Dataset schema is invalid, caused by: %(error)s",
error=str(err),
)
)