Files
superset2/superset/extensions/storage/utils.py
Michael S. Molina 34ebe3d22a feat(extensions): add a Storage API for extensions (#39171)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
Co-authored-by: Amin Ghadersohi <amin.ghadersohi@gmail.com>
Co-authored-by: Benyovszky Máté <benyovszky.mate.work@outlook.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 16:23:56 -03:00

113 lines
3.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.
"""
Shared helpers for extension storage (Tiers 2 and 3).
Centralizes cache-key construction and context/user resolution used by the
REST API (`api.py`) and the ambient `ephemeral`/`persistent` state accessors,
so isolation and scoping logic is defined once rather than duplicated per
call site.
"""
from __future__ import annotations
from typing import Any
from superset.extensions.context import get_current_extension_context
from superset.extensions.types import LoadedExtension
from superset.extensions.utils import get_extensions
from superset.utils.core import get_user_id
# Key separator
SEPARATOR = ":"
# Key prefix for extension storage
KEY_PREFIX = "superset-ext"
def build_cache_key(*parts: Any) -> str:
"""Build a namespaced cache key from parts."""
return SEPARATOR.join(str(part) for part in parts)
def build_storage_key(
extension_id: str, key: str, user_id: int | None, shared: bool
) -> str:
"""Build the cache key based on scope (user or shared)."""
if shared:
return build_cache_key(KEY_PREFIX, extension_id, "shared", key)
return build_cache_key(KEY_PREFIX, extension_id, "user", user_id, key)
def get_extension_or_404(extension_id: str) -> LoadedExtension | None:
"""Get extension by ID or return None if not found."""
extensions = get_extensions()
return extensions.get(extension_id)
def parse_ttl(body: dict[str, Any]) -> tuple[int | None, str | None]:
"""Parse TTL from a request body into a positive integer.
Only handles type coercion of the raw JSON value; the MAX_TTL business
rule is enforced once, in `ExtensionEphemeralDAO`, shared by both the
REST API and the ambient `ephemeral_state` accessor.
Returns:
(ttl, error_message) - error_message is set if the value is missing or invalid.
"""
if "ttl" not in body:
return None, "Field 'ttl' is required"
try:
ttl = int(body["ttl"])
except (TypeError, ValueError):
return None, "Field 'ttl' must be a positive integer"
if ttl <= 0:
return None, "Field 'ttl' must be a positive integer"
return ttl, None
def get_current_extension_id(caller: str) -> str:
"""Get the current extension ID from context.
:param caller: Name of the calling accessor (e.g. "ephemeral_state",
"persistent_state"), used in the error message.
"""
context = get_current_extension_context()
if context is None:
raise RuntimeError(
f"{caller} can only be used within an extension context. "
"Ensure this code is being executed during extension loading or "
"within an extension API request handler."
)
return context.extension.id
def get_current_user_id(caller: str) -> int:
"""Get the current authenticated user's ID.
:param caller: Name of the calling accessor (e.g. "ephemeral_state",
"persistent_state"), used in the error message.
"""
user_id = get_user_id()
if user_id is None:
raise RuntimeError(
f"{caller} requires an authenticated user. "
"Ensure the request has been authenticated."
)
return user_id