Files
superset2/superset/tasks/schemas.py
2026-02-10 06:56:14 -08:00

205 lines
7.9 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.
"""Task API schemas"""
from marshmallow import fields, Schema
from marshmallow.fields import Method
# RISON/JSON schemas for query parameters
get_delete_ids_schema = {"type": "array", "items": {"type": "string"}}
# Field descriptions
uuid_description = "The unique identifier (UUID) of the task"
task_key_description = "The task identifier used for deduplication"
dedup_key_description = (
"The hashed deduplication key used internally for task deduplication"
)
task_type_description = (
"The type of task (e.g., 'sql_execution', 'thumbnail_generation')"
)
task_name_description = "Human-readable name for the task"
status_description = "Current status of the task"
created_on_description = "Timestamp when the task was created"
changed_on_description = "Timestamp when the task was last updated"
started_at_description = "Timestamp when the task started execution"
ended_at_description = "Timestamp when the task completed or failed"
created_by_description = "User who created the task"
user_id_description = "ID of the user context for task execution"
payload_description = "Task-specific data in JSON format"
properties_description = (
"Runtime state and execution config. Contains: is_abortable, progress_percent, "
"progress_current, progress_total, error_message, exception_type, stack_trace, "
"timeout"
)
duration_seconds_description = (
"Duration in seconds - for finished tasks: execution time, "
"for running tasks: time since start, for pending: queue time"
)
scope_description = (
"Task scope: 'private' (user-specific), 'shared' (multi-user), "
"or 'system' (admin-only)"
)
subscriber_count_description = (
"Number of users subscribed to this task (for shared tasks)"
)
subscribers_description = "List of users subscribed to this task (for shared tasks)"
class UserSchema(Schema):
"""Schema for user information"""
id = fields.Int()
first_name = fields.String()
last_name = fields.String()
class TaskResponseSchema(Schema):
"""
Schema for task response.
Used for both list and detail endpoints.
"""
id = fields.Int(metadata={"description": "Internal task ID"})
uuid = fields.UUID(metadata={"description": uuid_description})
task_key = fields.String(metadata={"description": task_key_description})
dedup_key = fields.String(metadata={"description": dedup_key_description})
task_type = fields.String(metadata={"description": task_type_description})
task_name = fields.String(
metadata={"description": task_name_description}, allow_none=True
)
status = fields.String(metadata={"description": status_description})
created_on = fields.DateTime(metadata={"description": created_on_description})
created_on_delta_humanized = Method(
"get_created_on_delta_humanized",
metadata={"description": "Humanized time since creation"},
)
changed_on = fields.DateTime(metadata={"description": changed_on_description})
changed_by = fields.Nested(UserSchema, allow_none=True)
started_at = fields.DateTime(
metadata={"description": started_at_description}, allow_none=True
)
ended_at = fields.DateTime(
metadata={"description": ended_at_description}, allow_none=True
)
created_by = fields.Nested(UserSchema, allow_none=True)
user_id = fields.Int(metadata={"description": user_id_description}, allow_none=True)
payload = Method("get_payload_dict", metadata={"description": payload_description})
properties = Method(
"get_properties", metadata={"description": properties_description}
)
duration_seconds = Method(
"get_duration",
metadata={"description": duration_seconds_description},
)
scope = fields.String(metadata={"description": scope_description})
subscriber_count = Method(
"get_subscriber_count", metadata={"description": subscriber_count_description}
)
subscribers = Method(
"get_subscribers", metadata={"description": subscribers_description}
)
def get_payload_dict(self, obj: object) -> dict[str, object] | None:
"""Get payload as dictionary"""
return obj.payload_dict # type: ignore[attr-defined]
def get_properties(self, obj: object) -> dict[str, object]:
"""Get properties dict, filtering stack_trace if SHOW_STACKTRACE is disabled."""
from flask import current_app
properties = dict(obj.properties_dict) # type: ignore[attr-defined]
# Remove stack_trace unless SHOW_STACKTRACE is enabled
if not current_app.config.get("SHOW_STACKTRACE", False):
properties.pop("stack_trace", None)
return properties
def get_duration(self, obj: object) -> float | None:
"""Get duration in seconds"""
return obj.duration_seconds # type: ignore[attr-defined]
def get_created_on_delta_humanized(self, obj: object) -> str:
"""Get humanized time since creation"""
return obj.created_on_delta_humanized() # type: ignore[attr-defined]
def get_subscriber_count(self, obj: object) -> int:
"""Get number of subscribers"""
return obj.subscriber_count # type: ignore[attr-defined]
def get_subscribers(self, obj: object) -> list[dict[str, object]]:
"""Get list of subscribers with user info"""
subscribers = []
for sub in obj.subscribers: # type: ignore[attr-defined]
subscribers.append(
{
"user_id": sub.user_id,
"first_name": sub.user.first_name if sub.user else None,
"last_name": sub.user.last_name if sub.user else None,
"subscribed_at": sub.subscribed_at.isoformat()
if sub.subscribed_at
else None,
}
)
return subscribers
class TaskStatusResponseSchema(Schema):
"""Schema for task status response (lightweight for polling)"""
status = fields.String(metadata={"description": status_description})
class TaskCancelRequestSchema(Schema):
"""Schema for task cancellation request"""
force = fields.Boolean(
load_default=False,
metadata={
"description": "Force cancel the task for all subscribers (admin only). "
"Only applicable for shared tasks with multiple subscribers."
},
)
class TaskCancelResponseSchema(Schema):
"""Schema for task cancellation response"""
message = fields.String(metadata={"description": "Success or status message"})
action = fields.String(
metadata={
"description": "The action taken: 'aborted' (task terminated) or "
"'unsubscribed' (user removed from shared task)"
}
)
task = fields.Nested(TaskResponseSchema, allow_none=True)
openapi_spec_methods_override = {
"get": {"get": {"summary": "Get a task detail"}},
"get_list": {
"get": {
"summary": "Get a list of tasks",
"description": "Gets a list of tasks for the current user. "
"Use Rison or JSON query parameters for filtering, sorting, "
"pagination and for selecting specific columns and metadata.",
}
},
"info": {"get": {"summary": "Get metadata information about this API resource"}},
}